public function parse_htpasswd($passwdfile) { $lines = file($passwdfile); $users = array(); $i = 0; foreach ($lines as $line) { // if there is no :, assume this is a username with a newline if (MultiByte::strpos($line, ':') === false) { // append the next line to it $line = $line . $lines[$i + 1]; // unset the next line unset($lines[$i + 1]); } list($username, $password) = explode(':', $line); // trim the username and password $username = trim($username); $password = trim($password); if ($username == '' || $password == '') { continue; } $users[$username] = $password; $i++; } return $users; }
/** * Respond to Javascript callbacks * The name of this method is action_ajax_ followed by what you passed to the context parameter above. */ public function action_ajax_auto_tags( $handler ) { $selected = array(); if( isset( $handler->handler_vars['selected'] ) ) { $selected = Utils::single_array( $handler->handler_vars['selected'] ); } if( isset( $handler->handler_vars['term'] ) && MultiByte::strlen( $handler->handler_vars['term'] ) ) { $search = $handler->handler_vars['term'] . '%'; $tags = new Terms( DB::get_results( "SELECT * FROM {terms} WHERE vocabulary_id = :vid and LOWER(term_display) LIKE LOWER(:crit) ORDER BY term_display ASC", array( 'vid' => Tags::vocabulary()->id, 'crit' => $search ), 'Term' ) ); } else { $tags = Tags::vocabulary()->get_tree( 'term_display ASC' ); } $resp = array(); foreach ( $tags as $tag ) { $resp[] = MultiByte::strpos( $tag->term_display, ',' ) === false ? $tag->term_display : $tag->tag_text_searchable; } if( count( $selected ) ) { $resp = array_diff($resp, $selected ); } // Send the response // $ar = new AjaxResponse(); // $ar->data = $resp; // $ar->out(); echo json_encode( $resp ); }
function test_strtolower() { $this->assert_equal(MultiByte::strtolower(self::$test_str), mb_strtolower(mb_convert_encoding(self::$test_str, 'UTF-8', mb_detect_encoding(self::$test_str)), 'UTF-8')); printf("Test string: %s <br>", self::$test_str); printf("MultiByte strtolower: %s <br>", MultiByte::strtolower(self::$test_str)); printf("mbstring strtolower without detecting encoding: %s <br>", mb_strtolower(self::$test_str)); printf("mstring strtolower with detecting encoding: %s <br><br>", mb_strtolower(self::$test_str, mb_detect_encoding(self::$test_str))); }
/** * Perform all filtering, return new string. * @param string $str Input string. * @return string Filtered output string. */ public static function filter($str) { if (!MultiByte::valid_data($str)) { return ''; } else { do { $_str = $str; $str = self::strip_nulls($str); $str = self::strip_illegal_entities($str); $str = self::filter_html_elements($str); } while ($str != $_str); return $str; } }
/** * Create the admin theme instance * * @param string $page The admin page requested * @param string $type The content type included in the request */ public function setup_admin_theme($page, $type = '') { if (!isset($this->theme)) { $theme_dir = Plugins::filter('admin_theme_dir', Site::get_dir('admin_theme', true)); $this->theme = Themes::create('_admin', 'RawPHPEngine', $theme_dir); // Add some default template variables $this->set_admin_template_vars($this->theme); $this->theme->admin_type = $type; $this->theme->admin_page = $page; $this->theme->admin_page_url = $page == 'dashboard' ? URL::get('admin', 'page=') : URL::get('admin', 'page=' . $page); $this->theme->page = $page; $this->theme->admin_title = MultiByte::ucwords($page) . ($type != '' ? ' ' . MultiByte::ucwords($type) : ''); $this->theme->admin_title = isset($this->theme->mainmenu[$this->theme->admin_page]['text']) ? $this->theme->mainmenu[$this->theme->admin_page]['text'] : MultiByte::ucwords($page) . ($type != '' ? ' ' . MultiByte::ucwords($type) : ''); } }
/** * Turns a comma-separated string or array of terms into an array of Term objects * @param mixed $terms A comma-separated string or array of string terms * @param string $term_class The class of the Term object type to create from each string * @param Vocabulary $vocabulary An instance of the Vocabulary that might hold the terms. * Use existing term object data if found in the specified vocabulary. * @return Terms An instance of Terms contianing the specified Term objects **/ public static function parse( $terms, $term_class = 'Term', $vocabulary = null ) { if ( is_string( $terms ) ) { if ( '' === $terms ) { return new Terms(); } $terms = trim( MultiByte::str_replace( '"', '"', $terms ) ); // dirrty ;) $rez = array( '\\"'=>':__unlikely_quote__:', '\\\''=>':__unlikely_apos__:' ); $zer = array( ':__unlikely_quote__:'=>'"', ':__unlikely_apos__:'=>"'" ); // escape $tagstr = str_replace( array_keys( $rez ), $rez, $terms ); // match-o-matic preg_match_all( '/((("|((?<= )|^)\')\\S([^\\3]*?)\\3((?=[\\W])|$))|[^,])+/u', $tagstr, $matches ); // cleanup $terms = array_map( 'trim', $matches[0] ); $terms = preg_replace( array_fill( 0, count( $terms ), '/^(["\'])(((?!").)+)(\\1)$/' ), '$2', $terms ); // unescape $terms = str_replace( array_keys( $zer ), $zer, $terms ); // hooray } if ( is_array( $terms ) ) { if ( $vocabulary instanceof Vocabulary ) { foreach ( $terms as $k => $term ) { if ( $saved_term = $vocabulary->get_term( $term, $term_class ) ) { $terms[$k] = $saved_term; } else { $terms[$k] = new $term_class( $term ); } } //Utils::debug($terms); } else { array_walk( $terms, create_function( '&$tag', '$tag = new ' . $term_class . '($tag);' ) ); } return new Terms( $terms ); } return new Terms(); }
/** * Parses a search string for status, type, author, and tag keywords. Returns * an associative array which can be passed to Comments::get(). If multiple * authors, statuses, or types are specified, we assume an implicit OR * such that (e.g.) any author that matches would be returned. * * @param string $search_string The search string * @return array An associative array which can be passed to Comments::get() */ public static function search_to_get($search_string) { $keywords = array('author' => 1, 'status' => 1, 'type' => 1); // Comments::list_comment_statuses and list_comment_types return associative arrays with key/values // in the opposite order of the equivalent functions in Posts. Maybe we should change this? // In any case, we need to flip them for our purposes $statuses = array_flip(Comment::list_comment_statuses()); $types = array_flip(Comment::list_comment_types()); $arguments = array('name' => array(), 'status' => array(), 'type' => array()); $criteria = ''; $tokens = explode(' ', $search_string); foreach ($tokens as $token) { // check for a keyword:value pair if (preg_match('/^\\w+:\\S+$/u', $token)) { list($keyword, $value) = explode(':', $token); $keyword = strtolower($keyword); $value = MultiByte::strtolower($value); switch ($keyword) { case 'author': $arguments['name'][] = $value; break; case 'status': if (isset($statuses[$value])) { $arguments['status'][] = (int) $statuses[$value]; } break; case 'type': if (isset($types[$value])) { $arguments['type'][] = (int) $types[$value]; } break; } } else { $criteria .= $token . ' '; } } // flatten keys that have single-element or no-element arrays foreach ($arguments as $key => $arg) { switch (count($arg)) { case 0: unset($arguments[$key]); break; case 1: $arguments[$key] = $arg[0]; break; } } if ($criteria != '') { $arguments['criteria'] = $criteria; } return $arguments; }
/** * Returns a LogEntry or EventLog array based on supplied parameters. * By default,fetch as many entries as pagination allows and order them in a descending fashion based on timestamp. * * @todo Cache query results. * @param array $paramarry An associated array of parameters, or a querystring * @return array An array of LogEntry objects, or a single LogEntry object, depending on request */ public static function get($paramarray = array()) { $params = array(); $fns = array('get_results', 'get_row', 'get_value'); $select = ''; // Put incoming parameters into the local scope $paramarray = Utils::get_params($paramarray); $select_fields = LogEntry::default_fields(); if (!isset($paramarray['return_data'])) { unset($select_fields['data']); } foreach ($select_fields as $field => $value) { $select .= '' == $select ? "{log}.{$field}" : ", {log}.{$field}"; } // Default parameters. $orderby = 'ORDER BY timestamp DESC, id DESC'; $limit = Options::get('pagination'); // Get any full-query parameters $possible = array('orderby', 'fetch_fn', 'count', 'month_cts', 'nolimit', 'index', 'limit', 'offset'); foreach ($possible as $varname) { if (isset($paramarray[$varname])) { ${$varname} = $paramarray[$varname]; } } foreach ($paramarray as $key => $value) { if ('orderby' == $key) { $orderby = ' ORDER BY ' . $value; continue; } } // Transact on possible multiple sets of where information that is to be OR'ed if (isset($paramarray['where']) && is_array($paramarray['where'])) { $wheresets = $paramarray['where']; } else { $wheresets = array(array()); } $wheres = array(); $join = ''; if (isset($paramarray['where']) && is_string($paramarray['where'])) { $wheres[] = $paramarray['where']; } else { foreach ($wheresets as $paramset) { // Safety mechanism to prevent empty queries $where = array('1=1'); $paramset = array_merge((array) $paramarray, (array) $paramset); if (isset($paramset['id']) && is_numeric($paramset['id'])) { $where[] = "id= ?"; $params[] = $paramset['id']; } if (isset($paramset['user_id'])) { $where[] = "user_id= ?"; $params[] = $paramset['user_id']; } if (isset($paramset['severity']) && 'any' != LogEntry::severity_name($paramset['severity'])) { $where[] = "severity_id= ?"; $params[] = LogEntry::severity($paramset['severity']); } if (isset($paramset['type_id'])) { if (is_array($paramset['type_id'])) { $types = array_filter($paramset['type_id'], 'is_numeric'); if (count($types)) { $where[] = 'type_id IN (' . implode(',', $types) . ')'; } } else { $where[] = 'type_id = ?'; $params[] = $paramset['type_id']; } } if (isset($paramset['module'])) { if (!is_array($paramset['module'])) { $paramset['module'] = array($paramset['module']); } $where[] = 'type_id IN ( SELECT DISTINCT id FROM {log_types} WHERE module IN ( ' . implode(', ', array_fill(0, count($paramset['module']), '?')) . ' ) )'; $params = array_merge($params, $paramset['module']); } if (isset($paramset['type'])) { if (!is_array($paramset['type'])) { $paramset['type'] = array($paramset['type']); } $where[] = 'type_id IN ( SELECT DISTINCT id FROM {log_types} WHERE type IN ( ' . implode(', ', array_fill(0, count($paramset['type']), '?')) . ' ) )'; $params = array_merge($params, $paramset['type']); } if (isset($paramset['ip'])) { $where[] = 'ip = ?'; $params[] = $paramset['ip']; } /* do searching */ if (isset($paramset['criteria'])) { preg_match_all('/(?<=")(\\w[^"]*)(?=")|([:\\w]+)/u', $paramset['criteria'], $matches); foreach ($matches[0] as $word) { if (preg_match('%^id:(\\d+)$%i', $word, $special_crit)) { $where[] .= '(id = ?)'; $params[] = $special_crit[1]; } else { $where[] .= "( LOWER( message ) LIKE ? )"; $params[] = '%' . MultiByte::strtolower($word) . '%'; } } } /** * Build the pubdate * If we've got the day, then get the date. * If we've got the month, but no date, get the month. * If we've only got the year, get the whole year. * * @todo Ensure that we've actually got all the needed parts when we query on them */ if (isset($paramset['day'])) { $where[] = 'timestamp BETWEEN ? AND ?'; $start_date = sprintf('%d-%02d-%02d', $paramset['year'], $paramset['month'], $paramset['day']); $start_date = HabariDateTime::date_create($start_date); $params[] = $start_date->sql; $params[] = $start_date->modify('+1 day')->sql; //$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, $paramset['month'], $paramset['day'], $paramset['year'] ) ); //$params[] = date( 'Y-m-d H:i:s', mktime( 23, 59, 59, $paramset['month'], $paramset['day'], $paramset['year'] ) ); } elseif (isset($paramset['month'])) { $where[] = 'timestamp BETWEEN ? AND ?'; $start_date = sprintf('%d-%02d-%02d', $paramset['year'], $paramset['month'], 1); $start_date = HabariDateTime::date_create($start_date); $params[] = $start_date->sql; $params[] = $start_date->modify('+1 month')->sql; //$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, $paramset['month'], 1, $paramset['year'] ) ); //$params[] = date( 'Y-m-d H:i:s', mktime( 23, 59, 59, $paramset['month'] + 1, 0, $paramset['year'] ) ); } elseif (isset($paramset['year'])) { $where[] = 'timestamp BETWEEN ? AND ?'; $start_date = sprintf('%d-%02d-%02d', $paramset['year'], 1, 1); $start_date = HabariDateTime::date_create($start_date); $params[] = $start_date->sql; $params[] = $start_date->modify('+1 year')->sql; //$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, 1, 1, $paramset['year'] ) ); //$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, -1, 1, 1, $paramset['year'] + 1 ) ); } $wheres[] = ' (' . implode(' AND ', $where) . ') '; } } if (isset($index) && is_numeric($index)) { $offset = (intval($index) - 1) * intval($limit); } if (isset($fetch_fn)) { if (!in_array($fetch_fn, $fns)) { $fetch_fn = $fns[0]; } } else { $fetch_fn = $fns[0]; } if (isset($count)) { $select = "COUNT({$count})"; $fetch_fn = 'get_value'; $orderby = ''; } if (isset($limit)) { $limit = " LIMIT {$limit}"; if (isset($offset)) { $limit .= " OFFSET {$offset}"; } } // If the month counts are requested, replace the select clause if (isset($paramset['month_cts'])) { // @todo shouldn't this hand back to habari to convert to DateTime so it reflects the right timezone? $select = 'MONTH(FROM_UNIXTIME(timestamp)) AS month, YEAR(FROM_UNIXTIME(timestamp)) AS year, COUNT(*) AS ct'; $groupby = 'year, month'; $orderby = ' ORDER BY year, month'; } if (isset($nolimit) || isset($month_cts)) { $limit = ''; } $query = ' SELECT ' . $select . ' FROM {log} ' . $join; if (count($wheres) > 0) { $query .= ' WHERE ' . implode(" \nOR\n ", $wheres); } $query .= !isset($groupby) || $groupby == '' ? '' : ' GROUP BY ' . $groupby; $query .= $orderby . $limit; // Utils::debug( $paramarray, $fetch_fn, $query, $params ); DB::set_fetch_mode(PDO::FETCH_CLASS); DB::set_fetch_class('LogEntry'); $results = DB::$fetch_fn($query, $params, 'LogEntry'); // If the fetch callback function is not get_results, // return an EventLog ArrayObject filled with the results as LogEntry objects. if ('get_results' != $fetch_fn) { return $results; } elseif (is_array($results)) { $c = __CLASS__; $return_value = new $c($results); $return_value->get_param_cache = $paramarray; return $return_value; } }
/** * Either just display the login form; or check a user's credentials, and * create a session for them; or handle a password reset request. */ public function act_login() { // If we're a reset password request, do that. if (isset($_POST['submit_button']) && $_POST['submit_button'] === _t('Reset password')) { Utils::check_request_method(array('POST')); $name = $this->handler_vars['habari_username']; if ($name !== NULL) { if (!is_numeric($name) && ($user = User::get($name))) { $hash = Utils::random_password(); $user->info->password_reset = md5($hash); $user->info->commit(); $message = _t('Please visit %1$s to reset your password.', array(URL::get('user', array('page' => 'password_reset', 'id' => $user->id, 'hash' => $hash)))); Utils::mail($user->email, _t('[%1$s] Password reset request for %2$s', array(Options::get('title'), $user->displayname)), $message); } // Moving this inside the check for user existence would allow attackers to test usernames, so don't Session::notice(_t('A password reset request has been sent to the user.')); } } else { Utils::check_request_method(array('GET', 'HEAD', 'POST')); $name = $_POST['habari_username']; $pass = $_POST['habari_password']; if (NULL != $name || NULL != $pass) { $user = User::authenticate($name, $pass); if ($user instanceof User && FALSE != $user) { /* Successfully authenticated. */ // Timestamp last login date and time. $user->info->authenticate_time = date('Y-m-d H:i:s'); $user->update(); // Remove left over expired session error message. if (Session::has_errors('expired_session')) { Session::remove_error('expired_session'); } $login_session = Session::get_set('login'); if (!empty($login_session)) { /* Now that we know we're dealing with the same user, transfer the form data so he does not lose his request */ if (!empty($login_session['post_data'])) { Session::add_to_set('last_form_data', $last_form_data['post'], 'post'); } if (!empty($login_session['get_data'])) { Session::add_to_set('last_form_data', $last_form_data['get'], 'get'); } /* Redirect to the correct admin page */ $dest = explode('/', MultiByte::substr($login_session['original'], MultiByte::strpos($login_session['original'], 'admin/'))); if ('' == $dest[0]) { $login_dest = Site::get_url('admin'); } else { // Replace '?' with '&' in $dest[1] before call URL::get() // Therefore calling URL::get() with a query string $dest[1] = str_replace('?', '&', $dest[1]); $login_dest = URL::get('admin', 'page=' . $dest[1]); } } else { $login_session = null; $login_dest = Site::get_url('admin'); } // filter the destination $login_dest = Plugins::filter('login_redirect_dest', $login_dest, $user, $login_session); // finally, redirect to the destination Utils::redirect($login_dest); return TRUE; } /* Authentication failed. */ // Remove submitted password, see, we're secure! $_POST['habari_password'] = ''; $this->handler_vars['error'] = _t('Bad credentials'); } } // Display the login form. $this->login_form($name); }
/** * Trims longer phrases to shorter ones with elipsis in the middle * @param string $str The string to truncate * @param integer $len The length of the returned string * @param bool $middle Whether to place the ellipsis in the middle (true) or at the end (false) * @return string The truncated string */ public static function truncate($str, $len = 10, $middle = true) { // make sure $len is a positive integer if (!is_numeric($len) || 0 > $len) { return $str; } // if the string is less than the length specified, bail out if (MultiByte::strlen($str) <= $len) { return $str; } // okay. Shuold we place the ellipse in the middle? if ($middle) { // yes, so compute the size of each half of the string $len = round(($len - 3) / 2); // and place an ellipse in between the pieces return MultiByte::substr($str, 0, $len) . '…' . MultiByte::substr($str, -$len); } else { // no, the ellipse goes at the end $len = $len - 3; return MultiByte::substr($str, 0, $len) . '…'; } }
/** * Static helper function to quickly fetch an URL, with semantics similar to * PHP's file_get_contents. Does not support * * Returns the content on success or false if an error occurred. * * @param string $url The URL to fetch * @param bool $use_include_path whether to search the PHP include path first (unsupported) * @param resource $context a stream context to use (unsupported) * @param int $offset how many bytes to skip from the beginning of the result * @param int $maxlen how many bytes to return * @return string description */ public static function get_contents($url, $use_include_path = false, $context = null, $offset = 0, $maxlen = -1) { try { $rr = new RemoteRequest($url); if ($rr->execute() === true) { return $maxlen != -1 ? MultiByte::substr($rr->get_response_body(), $offset, $maxlen) : MultiByte::substr($rr->get_response_body(), $offset); } else { return false; } } catch (Exception $e) { // catch any exceptions to try and emulate file_get_contents() as closely as possible. // if you want more control over the errors, instantiate RemoteRequest manually return false; } }
/** * Receive a Pingback via XMLRPC * @param array $params An array of XMLRPC parameters from the remote call * @return string The success state of the pingback */ public function xmlrpc_pingback__ping($params) { try { list($source_uri, $target_uri) = $params; // This should really be done by an Habari core function $target_parse = InputFilter::parse_url($target_uri); $target_stub = $target_parse['path']; $base_url = Site::get_path('base', TRUE); if ('/' != $base_url) { $target_stub = str_replace($base_url, '', $target_stub); } $target_stub = trim($target_stub, '/'); if (strpos($target_stub, '?') !== FALSE) { list($target_stub, $query_string) = explode('?', $target_stub); } // Can this be used as a target? $target_slug = URL::parse($target_stub)->named_arg_values['slug']; if ($target_slug === FALSE) { throw new XMLRPCException(33); } // Does the target exist? $target_post = Post::get(array('slug' => $target_slug)); if ($target_post === FALSE) { throw new XMLRPCException(32); } // Is comment allowed? if ($target_post->info->comments_disabled) { throw new XMLRPCException(33); } // Is this Pingback already registered? if (Comments::get(array('post_id' => $target_post->id, 'url' => $source_uri, 'type' => Comment::PINGBACK))->count() > 0) { throw new XMLRPCException(48); } // Retrieve source contents $rr = new RemoteRequest($source_uri); $rr->execute(); if (!$rr->executed()) { throw new XMLRPCException(16); } $source_contents = $rr->get_response_body(); // encoding is converted into internal encoding. // @todo check BOM at beginning of file before checking for a charset attribute $habari_encoding = MultiByte::hab_encoding(); if (preg_match("/<meta[^>]+charset=([A-Za-z0-9\\-\\_]+)/i", $source_contents, $matches) !== FALSE && strtolower($habari_encoding) != strtolower($matches[1])) { $ret = MultiByte::convert_encoding($source_contents, $habari_encoding, $matches[1]); if ($ret !== FALSE) { $source_contents = $ret; } } // Find the page's title preg_match('/<title>(.*)<\\/title>/is', $source_contents, $matches); $source_title = $matches[1]; // Find the reciprocal links and their context preg_match('/<body[^>]*>(.+)<\\/body>/is', $source_contents, $matches); $source_contents_filtered = preg_replace('/\\s{2,}/is', ' ', strip_tags($matches[1], '<a>')); if (!preg_match('%.{0,100}?<a[^>]*?href\\s*=\\s*("|\'|)' . $target_uri . '\\1[^>]*?' . '>(.+?)</a>.{0,100}%s', $source_contents_filtered, $source_excerpt)) { throw new XMLRPCException(17); } /** Sanitize Data */ $source_excerpt = '...' . InputFilter::filter($source_excerpt[0]) . '...'; $source_title = InputFilter::filter($source_title); $source_uri = InputFilter::filter($source_uri); /* Sanitize the URL */ if (!empty($source_uri)) { $parsed = InputFilter::parse_url($source_uri); if ($parsed['is_relative']) { // guess if they meant to use an absolute link $parsed = InputFilter::parse_url('http://' . $source_uri); if (!$parsed['is_error']) { $source_uri = InputFilter::glue_url($parsed); } else { // disallow relative URLs $source_uri = ''; } } if ($parsed['is_pseudo'] || $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https') { // allow only http(s) URLs $source_uri = ''; } else { // reconstruct the URL from the error-tolerant parsing // http:moeffju.net/blog/ -> http://moeffju.net/blog/ $source_uri = InputFilter::glue_url($parsed); } } // Add a new pingback comment $pingback = new Comment(array('post_id' => $target_post->id, 'name' => $source_title, 'email' => '', 'url' => $source_uri, 'ip' => sprintf("%u", ip2long($_SERVER['REMOTE_ADDR'])), 'content' => $source_excerpt, 'status' => Comment::STATUS_UNAPPROVED, 'date' => HabariDateTime::date_create(), 'type' => Comment::PINGBACK)); $pingback->insert(); // Respond to the Pingback return 'The pingback has been registered'; } catch (XMLRPCException $e) { $e->output_fault_xml(); } }
/** * Grabs post data and inserts that data into the internal * handler_vars array, which eventually gets extracted into * the theme's ( and thereby the template_engine's ) local * symbol table for use in the theme's templates * * This is the default, generic function to grab posts. To * "filter" the posts retrieved, simply pass any filters to * the handler_vars variables associated with the post retrieval. * For instance, to filter by tag, ensure that handler_vars['tag'] * contains the tag to filter by. Simple as that. */ public function act_display($paramarray = array('user_filters' => array())) { Utils::check_request_method(array('GET', 'HEAD', 'POST')); // Get any full-query parameters $possible = array('user_filters', 'fallback', 'posts', 'post', 'content_type'); foreach ($possible as $varname) { if (isset($paramarray[$varname])) { ${$varname} = $paramarray[$varname]; } } /** * Since handler_vars no longer contains $_GET and $_POST, we have broken out our valid filters into * an array based upon where we should expect them to be. We then only merge those specific pieces together. * * These are ordered so that handler vars gets overwritten by POST, which gets overwritten by GET, should the * same key exist multiple places. This seemed logical to me at the time, but needs further thought. */ $where_filters = array(); $where_filters_hv = Controller::get_handler_vars()->filter_keys($this->valid_filters['handler_vars']); $where_filters_post = $_POST->filter_keys($this->valid_filters['POST']); $where_filters_get = $_GET->filter_keys($this->valid_filters['GET']); $where_filters = $where_filters_hv->merge($where_filters_post, $where_filters_get); $where_filters['vocabulary'] = array(); if (array_key_exists('tag', $where_filters)) { $tags = Tags::parse_url_tags($where_filters['tag']); $not_tag = $tags['exclude_tag']; $all_tag = $tags['include_tag']; if (count($not_tag) > 0) { $where_filters['vocabulary'] = array_merge($where_filters['vocabulary'], array(Tags::vocabulary()->name . ':not:term' => $not_tag)); } if (count($all_tag) > 0) { $where_filters['vocabulary'] = array_merge($where_filters['vocabulary'], array(Tags::vocabulary()->name . ':all:term' => $all_tag)); } $where_filters['tag_slug'] = Utils::slugify($where_filters['tag']); unset($where_filters['tag']); } if (!isset($_GET['preview'])) { $where_filters['status'] = Post::status('published'); } if (!isset($posts)) { $user_filters = Plugins::filter('template_user_filters', $user_filters); // Work around the tags parameters to Posts::get() being subsumed by the vocabulary parameter if (isset($user_filters['not:tag'])) { $user_filters['vocabulary'] = array(Tags::vocabulary()->name . ':not:term' => $user_filters['not:tag']); unset($user_filters['not:tag']); } if (isset($user_filters['tag'])) { $user_filters['vocabulary'] = array(Tags::vocabulary()->name . ':term_display' => $user_filters['tag']); unset($user_filters['tag']); } $where_filters = $where_filters->merge($user_filters); $where_filters = Plugins::filter('template_where_filters', $where_filters); $posts = Posts::get($where_filters); } $this->assign('posts', $posts); if ($posts !== false && count($posts) > 0) { if (count($posts) == 1) { $post = $posts instanceof Post ? $posts : reset($posts); Stack::add('body_class', Post::type_name($post->content_type) . '-' . $post->id); Stack::add('body_class', 'single'); } else { $post = reset($posts); Stack::add('body_class', 'multiple'); } $this->assign('post', $post); $type = Post::type_name($post->content_type); } elseif ($posts === false || isset($where_filters['page']) && $where_filters['page'] > 1 && count($posts) == 0) { if ($this->template_exists('404')) { $fallback = array('404'); // Replace template variables with the 404 rewrite rule $this->request->{URL::get_matched_rule()->name} = false; $this->request->{URL::set_404()->name} = true; $this->matched_rule = URL::get_matched_rule(); // 404 status header sent in act_display_404, but we're past // that, so send it now. header('HTTP/1.1 404 Not Found', true, 404); } else { $this->display('header'); echo '<h2>'; _e("Whoops! 404. The page you were trying to access is not really there. Please try again."); echo '</h2>'; header('HTTP/1.1 404 Not Found', true, 404); $this->display('footer'); die; } } $extract = $where_filters->filter_keys('page', 'type', 'id', 'slug', 'posttag', 'year', 'month', 'day', 'tag', 'tag_slug'); foreach ($extract as $key => $value) { ${$key} = $value; } $this->assign('page', isset($page) ? $page : 1); if (!isset($fallback)) { // Default fallbacks based on the number of posts $fallback = array('{$type}.{$id}', '{$type}.{$slug}', '{$type}.tag.{$posttag}'); if (count($posts) > 1) { $fallback[] = '{$type}.multiple'; $fallback[] = 'multiple'; } else { $fallback[] = '{$type}.single'; $fallback[] = 'single'; } } $searches = array('{$id}', '{$slug}', '{$year}', '{$month}', '{$day}', '{$type}', '{$tag}'); $replacements = array(isset($post) && $post instanceof Post ? $post->id : '-', isset($post) && $post instanceof Post ? $post->slug : '-', isset($year) ? $year : '-', isset($month) ? $month : '-', isset($day) ? $day : '-', isset($type) ? $type : '-', isset($tag_slug) ? $tag_slug : '-'); $fallback[] = 'home'; $fallback = Plugins::filter('template_fallback', $fallback, $posts, isset($post) ? $post : null); $fallback = array_values(array_unique(MultiByte::str_replace($searches, $replacements, $fallback))); for ($z = 0; $z < count($fallback); $z++) { if (MultiByte::strpos($fallback[$z], '{$posttag}') !== false && isset($post) && $post instanceof Post) { $replacements = array(); if ($alltags = $post->tags) { foreach ($alltags as $current_tag) { $replacements[] = MultiByte::str_replace('{$posttag}', $current_tag->term, $fallback[$z]); } array_splice($fallback, $z, 1, $replacements); } else { break; } } } return $this->display_fallback($fallback); }
public function test_detect_encoding() { $this->assert_equal(MultiByte::detect_encoding('foo'), 'ASCII'); $str = MultiByte::convert_encoding($this->test_strings['jis'], 'JIS'); $this->assert_equal(MultiByte::detect_encoding($str), 'JIS'); }
private static function save($xml, $type) { $timestamp = HabariDateTime::date_create('now'); $result = Cache::set('exportsnapshot__' . $timestamp->int, $xml, 0, true); // 0s expiration, but keep it forever if ($result) { $snapshots = Options::get('exportsnapshot__snapshots', array()); $snapshots[$timestamp->int] = array('size' => MultiByte::strlen($xml), 'type' => $type, 'ts' => $timestamp->int); Options::set('exportsnapshot__snapshots', $snapshots); return true; } else { return false; } }
/** * Returns a form for editing this post * @param string $context The context the form is being created in, most often 'admin' * @return FormUI A form appropriate for creating and updating this post. */ public function get_form($context) { $form = new FormUI('create-content'); $form->class[] = 'create'; $newpost = 0 === $this->id; // If the post has already been saved, add a link to its permalink if (!$newpost) { $post_links = $form->append('wrapper', 'post_links'); $permalink = $this->status != Post::status('published') ? $this->permalink . '?preview=1' : $this->permalink; $post_links->append('static', 'post_permalink', '<a href="' . $permalink . '" class="viewpost" >' . ($this->status != Post::status('published') ? _t('Preview Post') : _t('View Post')) . '</a>'); $post_links->class = 'container'; } // Store this post instance into a hidden field for later use when saving data $form->append('hidden', 'post', $this, _t('Title'), 'admincontrol_text'); // Create the Title field $form->append('text', 'title', 'null:null', _t('Title'), 'admincontrol_text'); $form->title->class[] = 'important'; $form->title->class[] = 'check-change'; $form->title->tabindex = 1; $form->title->value = $this->title_internal; // Create the silos if (count(Plugins::get_by_interface('MediaSilo'))) { $form->append('silos', 'silos'); $form->silos->silos = Media::dir(); } // Create the Content field $form->append('textarea', 'content', 'null:null', _t('Content'), 'admincontrol_textarea'); $form->content->class[] = 'resizable'; $form->content->class[] = 'check-change'; $form->content->tabindex = 2; $form->content->value = $this->content_internal; $form->content->raw = true; // Create the tags field $form->append('text', 'tags', 'null:null', _t('Tags, separated by, commas'), 'admincontrol_text'); $form->tags->class = 'check-change'; $form->tags->tabindex = 3; $tags = (array) $this->get_tags(); array_walk($tags, function (&$element, $key) { $element->term_display = MultiByte::strpos($element->term_display, ',') === false ? $element->term_display : $element->tag_text_searchable; }); $form->tags->value = implode(', ', $tags); // Create the splitter $publish_controls = $form->append('tabs', 'publish_controls'); // Create the publishing controls // pass "false" to list_post_statuses() so that we don't include internal post statuses $statuses = Post::list_post_statuses($this); unset($statuses[array_search('any', $statuses)]); $statuses = Plugins::filter('admin_publish_list_post_statuses', $statuses); $settings = $publish_controls->append('fieldset', 'settings', _t('Settings')); $settings->append('select', 'status', 'null:null', _t('Content State'), array_flip($statuses), 'tabcontrol_select'); $settings->status->value = $this->status; // hide the minor edit checkbox if the post is new if ($newpost) { $settings->append('hidden', 'minor_edit', 'null:null'); $settings->minor_edit->value = false; } else { $settings->append('checkbox', 'minor_edit', 'null:null', _t('Minor Edit'), 'tabcontrol_checkbox'); $settings->minor_edit->value = true; $form->append('hidden', 'modified', 'null:null')->value = $this->modified; } $settings->append('checkbox', 'comments_enabled', 'null:null', _t('Comments Allowed'), 'tabcontrol_checkbox'); $settings->comments_enabled->value = $this->info->comments_disabled ? false : true; $settings->append('text', 'pubdate', 'null:null', _t('Publication Time'), 'tabcontrol_text'); $settings->pubdate->value = $this->pubdate->format('Y-m-d H:i:s'); $settings->pubdate->helptext = _t('YYYY-MM-DD HH:MM:SS'); $settings->append('hidden', 'updated', 'null:null'); $settings->updated->value = $this->updated->int; $settings->append('text', 'newslug', 'null:null', _t('Content Address'), 'tabcontrol_text'); $settings->newslug->id = 'newslug'; $settings->newslug->value = $this->slug; // Create the button area $buttons = $form->append('fieldset', 'buttons'); $buttons->template = 'admincontrol_buttons'; $buttons->class[] = 'container'; $buttons->class[] = 'buttons'; $buttons->class[] = 'publish'; // Create the Save button $require_any = array('own_posts' => 'create', 'post_any' => 'create', 'post_' . Post::type_name($this->content_type) => 'create'); if ($newpost && User::identify()->can_any($require_any) || !$newpost && ACL::access_check($this->get_access(), 'edit')) { $buttons->append('submit', 'save', _t('Save'), 'admincontrol_submit'); $buttons->save->tabindex = 4; } // Add required hidden controls $form->append('hidden', 'content_type', 'null:null'); $form->content_type->id = 'content_type'; $form->content_type->value = $this->content_type; $form->append('hidden', 'post_id', 'null:null'); $form->post_id->id = 'id'; $form->post_id->value = $this->id; $form->append('hidden', 'slug', 'null:null'); $form->slug->value = $this->slug; $form->slug->id = 'originalslug'; $form->on_success(array($this, 'form_publish_success')); // Let plugins alter this form Plugins::act('form_publish', $form, $this, $context); $content_types = array_flip(Post::list_active_post_types()); Plugins::act('form_publish_' . Utils::slugify($content_types[$this->content_type], '_'), $form, $this, $context); // Return the form object return $form; }
/** * get_dir returns a complete filesystem path to the requested item * 'config_file' returns the complete path to the config.php file, including the filename * 'config' returns the path of the directory containing config.php * 'user' returns the path of the user directory * 'theme' returns the path of the site's active theme * @param string the name of the path item to return * @param bool whether to include a trailing slash. Default: No * @return string Path */ public static function get_dir( $name, $trail = false ) { $path = ''; switch ( strtolower( $name ) ) { case 'config_file': $path = Site::get_dir( 'config' ) . '/config.php'; break; case 'config': if ( self::$config_path ) { return self::$config_path; } self::$config_path = HABARI_PATH; $config_dirs = preg_replace( '/^' . preg_quote( HABARI_PATH, '/' ) . '\/user\/sites\/(.*)\/config.php/', '$1', Utils::glob( HABARI_PATH . '/user/sites/*/config.php' ) ); if ( empty( $config_dirs ) ) { return self::$config_path; } $server = InputFilter::parse_url( Site::get_url( 'habari' ) ); $server = ( isset( $server['port'] ) ) ? $server['port'] . '.' . $server['host'] . '.' : $server['host'] . '.'; $request = explode( '/', trim( $_SERVER['REQUEST_URI'], '/' ) ); $match = trim( $server, '.' ); $x = count( $request ); do { if ( in_array( $match, $config_dirs ) ) { self::$config_dir = $match; self::$config_path = HABARI_PATH . '/user/sites/' . self::$config_dir; self::$config_type = ( $x > 0 ) ? Site::CONFIG_SUBDOMAIN : Site::CONFIG_SUBDIR; break; } $match = MultiByte::substr( $match, MultiByte::strpos( $match, '.' ) + 1 ); $x--; } while ( MultiByte::strpos( $match, '.' ) !== false ); $path = self::$config_path; break; case 'user': if ( Site::get_dir( 'config' ) == HABARI_PATH ) { $path = HABARI_PATH . '/user'; } else { $path = Site::get_dir( 'config' ); } break; case 'theme': $theme = Themes::get_theme_dir(); if ( file_exists( Site::get_dir( 'config' ) . '/themes/' . $theme ) ) { $path = Site::get_dir( 'user' ) . '/themes/' . $theme; } elseif ( file_exists( HABARI_PATH . '/user/themes/' . $theme ) ) { $path = HABARI_PATH . '/user/themes/' . $theme; } elseif ( file_exists( HABARI_PATH . '/3rdparty/themes/' . $theme ) ) { $url = Site::get_url( 'habari' ) . '/3rdparty/themes/' . $theme; } else { $path = HABARI_PATH . '/system/themes/' . $theme; } break; case 'admin_theme': $path = HABARI_PATH . '/system/admin'; break; case 'vendor': $path = HABARI_PATH . '/system/vendor'; break; } $path .= Utils::trail( $trail ); $path = Plugins::filter( 'site_dir_' . $name, $path ); return $path; }
/** * Returns a LogEntry or EventLog array based on supplied parameters. * By default,fetch as many entries as pagination allows and order them in a descending fashion based on timestamp. * * @todo Cache query results. * @param array $paramarray An associated array of parameters, or a querystring * The following keys are supported: * - id => an entry id or array of post ids * - user_id => id of the logged in user for which to return entries * - severity => severity level for which to return entries * - type_id => the numeric id or array of ids for the type of entries for which which to return entries * - module => a name or array of names of modules for which to return entries * - type => a single type name or array of type names for which to return entries * - ip => the IP number for which to return entries * - criteria => a literal search string to match entry message content or a special search * - day => a day of entry creation, ignored if month and year are not specified * - month => a month of entry creation, ignored if year isn't specified * - year => a year of entry creation * - orderby => how to order the returned entries * - fetch_fn => the function used to fetch data, one of 'get_results', 'get_row', 'get_value' * - count => return the number of entries that would be returned by this request * - month_cts => return the number of entries created in each month * - nolimit => do not implicitly set limit * - limit => the maximum number of entries to return, implicitly set for many queries * - index => * - offset => amount by which to offset returned entries, used in conjunction with limit * - where => manipulate the generated WHERE clause * - return_data => set to return the data associated with the entry * * @return array An array of LogEntry objects, or a single LogEntry object, depending on request */ public static function get($paramarray = array()) { $params = array(); $fns = array('get_results', 'get_row', 'get_value'); $select_ary = array(); $select_distinct = array(); // Put incoming parameters into the local scope $paramarray = Utils::get_params($paramarray); if ($paramarray instanceof \ArrayIterator) { $paramarray = $paramarray->getArrayCopy(); } $select_fields = LogEntry::default_fields(); if (!isset($paramarray['return_data'])) { unset($select_fields['data']); } foreach ($select_fields as $field => $value) { if (preg_match('/(?:(?P<table>[\\w\\{\\}]+)\\.)?(?P<field>\\w+)(?:(?:\\s+as\\s+)(?P<alias>\\w+))?/i', $field, $fielddata)) { if (empty($fielddata['table'])) { $fielddata['table'] = '{log}'; } if (empty($fielddata['alias'])) { $fielddata['alias'] = $fielddata['field']; } } $select_ary[$fielddata['alias']] = "{$fielddata['table']}.{$fielddata['field']} AS {$fielddata['alias']}"; $select_distinct[$fielddata['alias']] = "{$fielddata['table']}.{$fielddata['field']}"; } // Transact on possible multiple sets of where information that is to be OR'ed if (isset($paramarray['where']) && is_array($paramarray['where'])) { $wheresets = $paramarray['where']; } else { $wheresets = array(array()); } $query = Query::create('{log}'); $query->select($select_ary); if (isset($paramarray['where']) && is_string($paramarray['where'])) { $query->where()->add($paramarray['where']); } foreach ($wheresets as $paramset) { $where = new QueryWhere(); $paramset = array_merge((array) $paramarray, (array) $paramset); if (isset($paramset['id'])) { $where->in('{log}.id', $paramset['id'], 'log_id', 'intval'); } if (isset($paramset['user_id'])) { $where->in('{log}.user_id', $paramset['user_id'], 'log_user_id', 'intval'); } if (isset($paramset['severity']) && 'any' != LogEntry::severity_name($paramset['severity'])) { $where->in('{log}.severity_id', $paramset['severity'], 'log_severity_id', function ($a) { return LogEntry::severity($a); }); } if (isset($paramset['type_id'])) { $where->in('{log}.type_id', $paramset['type_id'], 'log_type_id', 'intval'); } if (isset($paramset['module'])) { $paramset['module'] = Utils::single_array($paramset['module']); $qry = Query::create('{log_types}'); $qry->select('{log_types}.id')->distinct(); $qry->where()->in('{log_types}.module', $paramset['module'], 'log_subquery_module'); $where->in('{log}.type_id', $qry, 'log_module'); } if (isset($paramset['type'])) { $paramset['type'] = Utils::single_array($paramset['type']); $qry = Query::create('{log_types}'); $qry->select('{log_types}.id')->distinct(); $qry->where()->in('{log_types}.type', $paramset['type'], 'log_subquery_type'); $where->in('{log}.type_id', $qry, 'log_type'); } if (isset($paramset['ip'])) { $where->in('{log}.ip', $paramset['ip']); } /* do searching */ if (isset($paramset['criteria'])) { // this regex matches any unicode letters (\p{L}) or numbers (\p{N}) inside a set of quotes (but strips the quotes) OR not in a set of quotes preg_match_all('/(?<=")(\\w[^"]*)(?=")|([:\\w]+)/u', $paramset['criteria'], $matches); foreach ($matches[0] as $word) { if (preg_match('%^id:(\\d+)$%i', $word, $special_crit)) { $where->in('{log}.id', $special_crit[1], 'log_special_criteria'); } else { $crit_placeholder = $query->new_param_name('criteria'); $where->add("( LOWER( {log}.message ) LIKE :{$crit_placeholder}", array($crit_placeholder => '%' . MultiByte::strtolower($word) . '%')); } } } /** * Build the pubdate * If we've got the day, then get the date. * If we've got the month, but no date, get the month. * If we've only got the year, get the whole year. * * @todo Ensure that we've actually got all the needed parts when we query on them */ if (isset($paramset['day']) && isset($paramset['month']) && isset($paramset['year'])) { $start_date = sprintf('%d-%02d-%02d', $paramset['year'], $paramset['month'], $paramset['day']); $start_date = DateTime::create($start_date); $where->add('timestamp BETWEEN :start_date AND :end_date', array('start_date' => $start_date->sql, 'end_date' => $start_date->modify('+1 day -1 second')->sql)); } elseif (isset($paramset['month']) && isset($paramset['year'])) { $start_date = sprintf('%d-%02d-%02d', $paramset['year'], $paramset['month'], 1); $start_date = DateTime::create($start_date); $where->add('timestamp BETWEEN :start_date AND :end_date', array('start_date' => $start_date->sql, 'end_date' => $start_date->modify('+1 month -1 second')->sql)); } elseif (isset($paramset['year'])) { $start_date = sprintf('%d-%02d-%02d', $paramset['year'], 1, 1); $start_date = DateTime::create($start_date); $where->add('timestamp BETWEEN :start_date AND :end_date', array('start_date' => $start_date->sql, 'end_date' => $start_date->modify('+1 year -1 second')->sql)); } // Concatenate the WHERE clauses $query->where()->add($where); } // Default parameters. $orderby = 'timestamp DESC, id DESC'; // $limit = Options::get( 'pagination' ); // Get any full-query parameters $paramarray = new SuperGlobal($paramarray); $extract = $paramarray->filter_keys('orderby', 'fetch_fn', 'count', 'month_cts', 'nolimit', 'index', 'limit', 'offset'); foreach ($extract as $key => $value) { ${$key} = $value; } if (isset($index) && is_numeric($index)) { $offset = (intval($index) - 1) * intval($limit); } if (isset($fetch_fn)) { if (!in_array($fetch_fn, $fns)) { $fetch_fn = $fns[0]; } } else { $fetch_fn = $fns[0]; } if (isset($count)) { $query->set_select("COUNT({$count})"); $fetch_fn = isset($paramarray['fetch_fn']) ? $fetch_fn : 'get_value'; $orderby = null; $groupby = null; $having = null; } // If the month counts are requested, replace the select clause if (isset($paramset['month_cts'])) { // @todo shouldn't this hand back to habari to convert to DateTime so it reflects the right timezone? $query->set_select('MONTH(FROM_UNIXTIME(timestamp)) AS month, YEAR(FROM_UNIXTIME(timestamp)) AS year, COUNT(*) AS ct'); $groupby = 'year, month'; if (!isset($paramarray['orderby'])) { $orderby = 'year, month'; } } if (isset($nolimit) || isset($month_cts)) { $limit = null; } // Define the LIMIT, OFFSET, ORDER BY, GROUP BY if they exist if (isset($limit)) { $query->limit($limit); } if (isset($offset)) { $query->offset($offset); } if (isset($orderby)) { $query->orderby($orderby); } if (isset($groupby)) { $query->groupby($groupby); } /* if(isset($paramarray['type'])) { print_r($query->params()); print_r($query->get());die(); } */ /* All SQL parts are constructed, on to real business! */ DB::set_fetch_mode(\PDO::FETCH_CLASS); DB::set_fetch_class('LogEntry'); $results = DB::$fetch_fn($query->get(), $query->params(), 'LogEntry'); // If the fetch callback function is not get_results, // return an EventLog ArrayObject filled with the results as LogEntry objects. if ('get_results' != $fetch_fn) { return $results; } elseif (is_array($results)) { $c = __CLASS__; $return_value = new $c($results); $return_value->get_param_cache = $paramarray; return $return_value; } }
/** * Returns a post or posts based on supplied parameters. * @todo <b>THIS CLASS SHOULD CACHE QUERY RESULTS!</b> * * @param array $paramarray An associative array of parameters, or a querystring. * The following keys are supported: * - id => a post id or array of post ids * - not:id => a post id or array of post ids to exclude * - slug => a post slug or array of post slugs * - not:slug => a post slug or array of post slugs to exclude * - user_id => an author id or array of author ids * - content_type => a post content type or array post content types * - not:content_type => a post content type or array post content types to exclude * - status => a post status, an array of post statuses, or 'any' for all statuses * - year => a year of post publication * - month => a month of post publication, ignored if year is not specified * - day => a day of post publication, ignored if month and year are not specified * - before => a timestamp to compare post publication dates * - after => a timestamp to compare post publication dates * - month_cts => return the number of posts published in each month * - criteria => a literal search string to match post content * - title => an exact case-insensitive match to a post title * - title_search => a search string that acts only on the post title * - has:info => a post info key or array of post info keys, which should be present * - all:info => a post info key and value pair or array of post info key and value pairs, which should all be present and match * - not:all:info => a post info key and value pair or array of post info key and value pairs, to exclude if all are present and match * - any:info => a post info key and value pair or array of post info key and value pairs, any of which can match * - not:any:info => a post info key and value pair or array of post info key and value pairs, to exclude if any are present and match * - vocabulary => an array describing parameters related to vocabularies attached to posts. This can be one of two forms: * - object-based, in which an array of Term objects are passed * - any => posts associated with any of the terms are returned * - all => posts associated with all of the terms are returned * - not => posts associated with none of the terms are returned * - property-based, in which an array of vocabulary names and associated fields are passed * - vocabulary_name:term => a vocabulary name and term slug pair or array of vocabulary name and term slug pairs, any of which can be associated with the posts * - vocabulary_name:term_display => a vocabulary name and term display pair or array of vocabulary name and term display pairs, any of which can be associated with the posts * - vocabulary_name:not:term => a vocabulary name and term slug pair or array of vocabulary name and term slug pairs, none of which can be associated with the posts * - vocabulary_name:not:term_display => a vocabulary name and term display pair or array of vocabulary name and term display pairs, none of which can be associated with the posts * - vocabulary_name:all:term => a vocabulary name and term slug pair or array of vocabulary name and term slug pairs, all of which must be associated with the posts * - vocabulary_name:all:term_display => a vocabulary name and term display pair or array of vocabulary name and term display pairs, all of which must be associated with the posts * - limit => the maximum number of posts to return, implicitly set for many queries * - nolimit => do not implicitly set limit * - offset => amount by which to offset returned posts, used in conjunction with limit * - page => the 'page' of posts to return when paging, sets the appropriate offset * - count => return the number of posts that would be returned by this request * - orderby => how to order the returned posts * - groupby => columns by which to group the returned posts, for aggregate functions * - having => for selecting posts based on an aggregate function * - where => manipulate the generated WHERE clause. Currently broken, see https://trac.habariproject.org/habari/ticket/1383 * - add_select => an array of clauses to be added to the generated SELECT clause. * - fetch_fn => the function used to fetch data, one of 'get_results', 'get_row', 'get_value', 'get_query' * * Further description of parameters, including usage examples, can be found at * http://wiki.habariproject.org/en/Dev:Retrieving_Posts * * @return array An array of Post objects, or a single post object, depending on request */ public static function get($paramarray = array()) { // If $paramarray is a querystring, convert it to an array $paramarray = Utils::get_params($paramarray); // let plugins alter the param array before we use it. could be useful for modifying search results, etc. $paramarray = Plugins::filter('posts_get_paramarray', $paramarray); $join_params = array(); $params = array(); $fns = array('get_results', 'get_row', 'get_value', 'get_query'); $select_ary = array(); // Default fields to select, everything by default foreach (Post::default_fields() as $field => $value) { $select_ary[$field] = "{posts}.{$field} AS {$field}"; $select_distinct[$field] = "{posts}.{$field}"; } // Default parameters $orderby = 'pubdate DESC'; // Define the WHERE sets to process and OR in the final SQL statement if (isset($paramarray['where']) && is_array($paramarray['where'])) { $wheresets = $paramarray['where']; } else { $wheresets = array(array()); } /* Start building the WHERE clauses */ $wheres = array(); $joins = array(); // If the request as a textual WHERE clause, skip the processing of the $wheresets since it's empty if (isset($paramarray['where']) && is_string($paramarray['where'])) { $wheres[] = $paramarray['where']; } else { foreach ($wheresets as $paramset) { // Safety mechanism to prevent empty queries $where = array(); $paramset = array_merge((array) $paramarray, (array) $paramset); // $nots= preg_grep( '%^not:(\w+)$%iu', (array) $paramset ); if (isset($paramset['id'])) { if (is_array($paramset['id'])) { array_walk($paramset['id'], create_function('&$a,$b', '$a = intval( $a );')); $where[] = "{posts}.id IN (" . implode(',', array_fill(0, count($paramset['id']), '?')) . ")"; $params = array_merge($params, $paramset['id']); } else { $where[] = "{posts}.id = ?"; $params[] = (int) $paramset['id']; } } if (isset($paramset['not:id'])) { if (is_array($paramset['not:id'])) { array_walk($paramset['not:id'], create_function('&$a,$b', '$a = intval( $a );')); $where[] = "{posts}.id NOT IN (" . implode(',', array_fill(0, count($paramset['not:id']), '?')) . ")"; $params = array_merge($params, $paramset['not:id']); } else { $where[] = "{posts}.id != ?"; $params[] = (int) $paramset['not:id']; } } if (isset($paramset['status']) && $paramset['status'] != 'any' && 0 !== $paramset['status']) { if (is_array($paramset['status'])) { // remove 'any' from the list if we have an array $paramset['status'] = array_diff($paramset['status'], array('any')); array_walk($paramset['status'], create_function('&$a,$b', '$a = Post::status( $a );')); $where[] = "{posts}.status IN (" . implode(',', array_fill(0, count($paramset['status']), '?')) . ")"; $params = array_merge($params, $paramset['status']); } else { $where[] = "{posts}.status = ?"; $params[] = (int) Post::status($paramset['status']); } } if (isset($paramset['content_type']) && $paramset['content_type'] != 'any' && 0 !== $paramset['content_type']) { if (is_array($paramset['content_type'])) { // remove 'any' from the list if we have an array $paramset['content_type'] = array_diff($paramset['content_type'], array('any')); array_walk($paramset['content_type'], create_function('&$a,$b', '$a = Post::type( $a );')); $where[] = "{posts}.content_type IN (" . implode(',', array_fill(0, count($paramset['content_type']), '?')) . ")"; $params = array_merge($params, $paramset['content_type']); } else { $where[] = "{posts}.content_type = ?"; $params[] = (int) Post::type($paramset['content_type']); } } if (isset($paramset['not:content_type'])) { if (is_array($paramset['not:content_type'])) { array_walk($paramset['not:content_type'], create_function('&$a,$b', '$a = Post::type( $a );')); $where[] = "{posts}.content_type NOT IN (" . implode(',', array_fill(0, count($paramset['not:content_type']), '?')) . ")"; $params = array_merge($params, $paramset['not:content_type']); } else { $where[] = "{posts}.content_type != ?"; $params[] = (int) Post::type($paramset['not:content_type']); } } if (isset($paramset['slug'])) { if (is_array($paramset['slug'])) { $where[] = "{posts}.slug IN (" . implode(',', array_fill(0, count($paramset['slug']), '?')) . ")"; $params = array_merge($params, $paramset['slug']); } else { $where[] = "{posts}.slug = ?"; $params[] = (string) $paramset['slug']; } } if (isset($paramset['not:slug'])) { if (is_array($paramset['not:slug'])) { $where[] = "{posts}.slug NOT IN (" . implode(',', array_fill(0, count($paramset['not:slug']), '?')) . ")"; $params = array_merge($params, $paramset['not:slug']); } else { $where[] = "{posts}.slug != ?"; $params[] = (string) $paramset['not:slug']; } } if (isset($paramset['user_id']) && 0 !== $paramset['user_id']) { if (is_array($paramset['user_id'])) { array_walk($paramset['user_id'], create_function('&$a,$b', '$a = intval( $a );')); $where[] = "{posts}.user_id IN (" . implode(',', array_fill(0, count($paramset['user_id']), '?')) . ")"; $params = array_merge($params, $paramset['user_id']); } else { $where[] = "{posts}.user_id = ?"; $params[] = (int) $paramset['user_id']; } } if (isset($paramset['vocabulary'])) { if (is_string($paramset['vocabulary'])) { $paramset['vocabulary'] = Utils::get_params($paramset['vocabulary']); } // parse out the different formats we accept arguments in into a single mutli-dimensional array of goodness $paramset['vocabulary'] = self::vocabulary_params($paramset['vocabulary']); $object_id = Vocabulary::object_type_id('post'); $all = array(); $any = array(); $not = array(); if (isset($paramset['vocabulary']['all'])) { $all = $paramset['vocabulary']['all']; } if (isset($paramset['vocabulary']['any'])) { $any = $paramset['vocabulary']['any']; } if (isset($paramset['vocabulary']['not'])) { $not = $paramset['vocabulary']['not']; } foreach ($all as $vocab => $value) { foreach ($value as $field => $terms) { // we only support these fields to search by if (!in_array($field, array('id', 'term', 'term_display'))) { continue; } $joins['term2post_posts'] = ' JOIN {object_terms} ON {posts}.id = {object_terms}.object_id'; $joins['terms_term2post'] = ' JOIN {terms} ON {object_terms}.term_id = {terms}.id'; $joins['terms_vocabulary'] = ' JOIN {vocabularies} ON {terms}.vocabulary_id = {vocabularies}.id'; $where[] = '{vocabularies}.name = ? AND {terms}.' . $field . ' IN ( ' . Utils::placeholder_string($terms) . ' ) AND {object_terms}.object_type_id = ?'; $params[] = $vocab; $params = array_merge($params, $terms); $params[] = $object_id; } // this causes no posts to match if combined with 'any' below and should be re-thought... somehow $groupby = implode(',', $select_distinct); $having = 'count(*) = ' . count($terms); } foreach ($any as $vocab => $value) { foreach ($value as $field => $terms) { // we only support these fields to search by if (!in_array($field, array('id', 'term', 'term_display'))) { continue; } $joins['term2post_posts'] = ' JOIN {object_terms} ON {posts}.id = {object_terms}.object_id'; $joins['terms_term2post'] = ' JOIN {terms} ON {object_terms}.term_id = {terms}.id'; $joins['terms_vocabulary'] = ' JOIN {vocabularies} ON {terms}.vocabulary_id = {vocabularies}.id'; $where[] = '{vocabularies}.name = ? AND {terms}.' . $field . ' IN ( ' . Utils::placeholder_string($terms) . ' ) AND {object_terms}.object_type_id = ?'; $params[] = $vocab; $params = array_merge($params, $terms); $params[] = $object_id; } } foreach ($not as $vocab => $value) { foreach ($value as $field => $terms) { // we only support these fields to search by if (!in_array($field, array('id', 'term', 'term_display'))) { continue; } $where[] = 'NOT EXISTS ( SELECT 1 FROM {object_terms} JOIN {terms} ON {terms}.id = {object_terms}.term_id JOIN {vocabularies} ON {terms}.vocabulary_id = {vocabularies}.id WHERE {terms}.' . $field . ' IN (' . Utils::placeholder_string($terms) . ') AND {object_terms}.object_id = {posts}.id AND {object_terms}.object_type_id = ? AND {vocabularies}.name = ? )'; $params = array_merge($params, array_values($terms)); $params[] = $object_id; $params[] = $vocab; } } } if (isset($paramset['criteria'])) { // this regex matches any unicode letters (\p{L}) or numbers (\p{N}) inside a set of quotes (but strips the quotes) OR not in a set of quotes preg_match_all('/(?<=")([\\p{L}\\p{N}]+[^"]*)(?=")|([\\p{L}\\p{N}]+)/u', $paramset['criteria'], $matches); foreach ($matches[0] as $word) { $where[] .= "( LOWER( {posts}.title ) LIKE ? OR LOWER( {posts}.content ) LIKE ?)"; $params[] = '%' . MultiByte::strtolower($word) . '%'; $params[] = '%' . MultiByte::strtolower($word) . '%'; // Not a typo (there are two ? in the above statement) } } if (isset($paramset['title'])) { $where[] .= "LOWER( {posts}.title ) LIKE ?"; $params[] = MultiByte::strtolower($paramset['title']); } if (isset($paramset['title_search'])) { // this regex matches any unicode letters (\p{L}) or numbers (\p{N}) inside a set of quotes (but strips the quotes) OR not in a set of quotes preg_match_all('/(?<=")([\\p{L}\\p{N}]+[^"]*)(?=")|([\\p{L}\\p{N}]+)/u', $paramset['title_search'], $matches); foreach ($matches[0] as $word) { $where[] .= " LOWER( {posts}.title ) LIKE ? "; $params[] = '%' . MultiByte::strtolower($word) . '%'; } } if (isset($paramset['all:info']) || isset($paramset['info'])) { // merge the two possibile calls together $infos = array_merge(isset($paramset['all:info']) ? $paramset['all:info'] : array(), isset($paramset['info']) ? $paramset['info'] : array()); if (Utils::is_traversable($infos)) { $pi_count = 0; foreach ($infos as $info_key => $info_value) { $pi_count++; $joins['info_' . $info_key] = " LEFT JOIN {postinfo} ipi{$pi_count} ON {posts}.id = ipi{$pi_count}.post_id AND ipi{$pi_count}.name = ? AND ipi{$pi_count}.value = ?"; $join_params[] = $info_key; $join_params[] = $info_value; $where[] = "ipi{$pi_count}.name <> ''"; $select_ary["info_{$info_key}_value"] = "ipi{$pi_count}.value AS info_{$info_key}_value"; $select_distinct["info_{$info_key}_value"] = "info_{$info_key}_value"; } } } if (isset($paramset['any:info'])) { if (Utils::is_traversable($paramset['any:info'])) { $pi_count = 0; $pi_where = array(); foreach ($paramset['any:info'] as $info_key => $info_value) { $pi_count++; $join_params[] = $info_key; if (is_array($info_value)) { $joins['any_info_' . $info_key] = " LEFT JOIN {postinfo} aipi{$pi_count} ON {posts}.id = aipi{$pi_count}.post_id AND aipi{$pi_count}.name = ? AND aipi{$pi_count}.value IN (" . Utils::placeholder_string(count($info_value)) . ")"; $join_params = array_merge($join_params, $info_value); } else { $joins['any_info_' . $info_key] = " LEFT JOIN {postinfo} aipi{$pi_count} ON {posts}.id = aipi{$pi_count}.post_id AND aipi{$pi_count}.name = ? AND aipi{$pi_count}.value = ?"; $join_params[] = $info_value; } $pi_where[] = "aipi{$pi_count}.name <> ''"; $select_ary["info_{$info_key}_value"] = "aipi{$pi_count}.value AS info_{$info_key}_value"; $select_distinct["info_{$info_key}_value"] = "info_{$info_key}_value"; } $where[] = '(' . implode(' OR ', $pi_where) . ')'; } } if (isset($paramset['has:info'])) { $the_ins = array(); $has_info = Utils::single_array($paramset['has:info']); $pi_count = 0; $pi_where = array(); foreach ($has_info as $info_name) { $pi_count++; $joins['has_info_' . $info_name] = " LEFT JOIN {postinfo} hipi{$pi_count} ON {posts}.id = hipi{$pi_count}.post_id AND hipi{$pi_count}.name = ?"; $join_params[] = $info_name; $pi_where[] = "hipi{$pi_count}.name <> ''"; $select_ary["info_{$info_name}_value"] = "hipi{$pi_count}.value AS info_{$info_name}_value"; $select_distinct["info_{$info_name}_value"] = "info_{$info_name}_value"; } $where[] = '(' . implode(' OR ', $pi_where) . ')'; } if (isset($paramset['not:all:info']) || isset($paramset['not:info'])) { // merge the two possible calls together $infos = array_merge(isset($paramset['not:all:info']) ? $paramset['not:all:info'] : array(), isset($paramset['not:info']) ? $paramset['not:info'] : array()); if (Utils::is_traversable($infos)) { $the_ins = array(); foreach ($infos as $info_key => $info_value) { $the_ins[] = ' ({postinfo}.name = ? AND {postinfo}.value = ? ) '; $params[] = $info_key; $params[] = $info_value; } $where[] = ' {posts}.id NOT IN ( SELECT post_id FROM {postinfo} WHERE ( ' . implode(' OR ', $the_ins) . ' ) GROUP BY post_id HAVING COUNT(*) = ' . count($infos) . ' ) '; // see that hard-coded number? sqlite wets itself if we use a bound parameter... don't change that } } if (isset($paramset['not:any:info'])) { if (Utils::is_traversable($paramset['not:any:info'])) { foreach ($paramset['not:any:info'] as $info_key => $info_value) { $the_ins[] = ' ({postinfo}.name = ? AND {postinfo}.value = ? ) '; $params[] = $info_key; $params[] = $info_value; } $where[] = ' {posts}.id NOT IN ( SELECT post_id FROM {postinfo} WHERE ( ' . implode(' OR ', $the_ins) . ' ) ) '; } } /** * Build the statement needed to filter by pubdate: * If we've got the day, then get the date; * If we've got the month, but no date, get the month; * If we've only got the year, get the whole year. */ if (isset($paramset['day']) && isset($paramset['month']) && isset($paramset['year'])) { $where[] = 'pubdate BETWEEN ? AND ?'; $start_date = sprintf('%d-%02d-%02d', $paramset['year'], $paramset['month'], $paramset['day']); $start_date = HabariDateTime::date_create($start_date); $params[] = $start_date->sql; $params[] = $start_date->modify('+1 day')->sql; //$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, $paramset['month'], $paramset['day'], $paramset['year'] ) ); //$params[] = date( 'Y-m-d H:i:s', mktime( 23, 59, 59, $paramset['month'], $paramset['day'], $paramset['year'] ) ); } elseif (isset($paramset['month']) && isset($paramset['year'])) { $where[] = 'pubdate BETWEEN ? AND ?'; $start_date = sprintf('%d-%02d-%02d', $paramset['year'], $paramset['month'], 1); $start_date = HabariDateTime::date_create($start_date); $params[] = $start_date->sql; $params[] = $start_date->modify('+1 month')->sql; //$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, $paramset['month'], 1, $paramset['year'] ) ); //$params[] = date( 'Y-m-d H:i:s', mktime( 23, 59, 59, $paramset['month'] + 1, 0, $paramset['year'] ) ); } elseif (isset($paramset['year'])) { $where[] = 'pubdate BETWEEN ? AND ?'; $start_date = sprintf('%d-%02d-%02d', $paramset['year'], 1, 1); $start_date = HabariDateTime::date_create($start_date); $params[] = $start_date->sql; $params[] = $start_date->modify('+1 year')->sql; //$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, 1, 1, $paramset['year'] ) ); //$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, -1, 1, 1, $paramset['year'] + 1 ) ); } if (isset($paramset['after'])) { $where[] = 'pubdate > ?'; $params[] = HabariDateTime::date_create($paramset['after'])->sql; } if (isset($paramset['before'])) { $where[] = 'pubdate < ?'; $params[] = HabariDateTime::date_create($paramset['before'])->sql; } // Concatenate the WHERE clauses if (count($where) > 0) { $wheres[] = ' (' . implode(' AND ', $where) . ') '; } } } // Only show posts to which the current user has permission if (isset($paramset['ignore_permissions'])) { $master_perm_where = ''; } else { // This set of wheres will be used to generate a list of post_ids that this user can read $perm_where = array(); $perm_where_denied = array(); $params_where = array(); $where = array(); // Get the tokens that this user is granted or denied access to read $read_tokens = isset($paramset['read_tokens']) ? $paramset['read_tokens'] : ACL::user_tokens(User::identify(), 'read', true); $deny_tokens = isset($paramset['deny_tokens']) ? $paramset['deny_tokens'] : ACL::user_tokens(User::identify(), 'deny', true); // If a user can read any post type, let him if (User::identify()->can('post_any', 'read')) { $perm_where = array('post_any' => '(1=1)'); } else { // If a user can read his own posts, let him if (User::identify()->can('own_posts', 'read')) { $perm_where['own_posts_id'] = '{posts}.user_id = ?'; $params_where[] = User::identify()->id; } // If a user can read specific post types, let him $permitted_post_types = array(); foreach (Post::list_active_post_types() as $name => $posttype) { if (User::identify()->can('post_' . Utils::slugify($name), 'read')) { $permitted_post_types[] = $posttype; } } if (count($permitted_post_types) > 0) { $perm_where[] = '{posts}.content_type IN (' . implode(',', $permitted_post_types) . ')'; } // If a user can read posts with specific tokens, let him if (count($read_tokens) > 0) { $joins['post_tokens__allowed'] = ' LEFT JOIN {post_tokens} pt_allowed ON {posts}.id= pt_allowed.post_id AND pt_allowed.token_id IN (' . implode(',', $read_tokens) . ')'; $perm_where['perms_join_null'] = 'pt_allowed.post_id IS NOT NULL'; } // If a user has access to read other users' unpublished posts, let him if (User::identify()->can('post_unpublished', 'read')) { $perm_where[] = '({posts}.status <> ? AND {posts}.user_id <> ?)'; $params_where[] = Post::status('published'); $params_where[] = User::identify()->id; } } $params_where_denied = array(); // If a user is denied access to all posts, do so if (User::identify()->cannot('post_any')) { $perm_where_denied = array('(1=0)'); } else { // If a user is denied read access to specific post types, deny him $denied_post_types = array(); foreach (Post::list_active_post_types() as $name => $posttype) { if (User::identify()->cannot('post_' . Utils::slugify($name))) { $denied_post_types[] = $posttype; } } if (count($denied_post_types) > 0) { $perm_where_denied[] = '{posts}.content_type NOT IN (' . implode(',', $denied_post_types) . ')'; } // If a user is denied access to read other users' unpublished posts, deny it if (User::identify()->cannot('post_unpublished')) { $perm_where_denied[] = '({posts}.status = ? OR {posts}.user_id = ?)'; $params_where_denied[] = Post::status('published'); $params_where_denied[] = User::identify()->id; } } // This doesn't work yet because you can't pass these arrays by reference Plugins::act('post_get_perm_where', $perm_where, $params_where, $paramarray); Plugins::act('post_get_perm_where_denied', $perm_where_denied, $params_where_denied, $paramarray); // Set up the merge params $merge_params = array($join_params, $params); // If there are granted permissions to check, add them to the where clause if (count($perm_where) == 0 && !isset($joins['post_tokens__allowed'])) { // You have no grants. You get no posts. $where['perms_granted'] = '(1=0)'; } elseif (count($perm_where) > 0) { $where['perms_granted'] = ' (' . implode(' OR ', $perm_where) . ') '; $merge_params[] = $params_where; } if (count($deny_tokens) > 0) { $joins['post_tokens__denied'] = ' LEFT JOIN {post_tokens} pt_denied ON {posts}.id= pt_denied.post_id AND pt_denied.token_id IN (' . implode(',', $deny_tokens) . ')'; $perm_where_denied['perms_join_null'] = 'pt_denied.post_id IS NULL'; } // If there are denied permissions to check, add them to the where clause if (count($perm_where_denied) > 0) { $where['perms_denied'] = ' (' . implode(' AND ', $perm_where_denied) . ') '; $merge_params[] = $params_where_denied; } // Merge the params $params = call_user_func_array('array_merge', $merge_params); // AND the separate permission-related WHERE clauses $master_perm_where = implode(' AND ', $where); } // Extract the remaining parameters which will be used onwards // For example: page number, fetch function, limit $paramarray = new SuperGlobal($paramarray); $extract = $paramarray->filter_keys('page', 'fetch_fn', 'count', 'orderby', 'groupby', 'limit', 'offset', 'nolimit', 'having', 'add_select'); foreach ($extract as $key => $value) { ${$key} = $value; } // Define the LIMIT if it does not exist, unless specific posts are requested or we're getting the monthly counts if (!isset($limit) && !isset($paramset['id']) && !isset($paramset['slug']) && !isset($paramset['month_cts'])) { $limit = Options::get('pagination') ? (int) Options::get('pagination') : 5; } elseif (!isset($limit)) { $selected_posts = 0; if (isset($paramset['id'])) { $selected_posts += count(Utils::single_array($paramset['id'])); } if (isset($paramset['slug'])) { $selected_posts += count(Utils::single_array($paramset['slug'])); } $limit = $selected_posts > 0 ? $selected_posts : ''; } // Calculate the OFFSET based on the page number if (isset($page) && is_numeric($page) && !isset($paramset['offset'])) { $offset = (intval($page) - 1) * intval($limit); } /** * Determine which fetch function to use: * If it is specified, make sure it is valid (based on the $fns array defined at the beginning of this function); * Else, use 'get_results' which will return a Posts array of Post objects. */ if (isset($fetch_fn)) { if (!in_array($fetch_fn, $fns)) { $fetch_fn = $fns[0]; } } else { $fetch_fn = $fns[0]; } // If the orderby has a function in it, try to create a select field for it with an alias if (strpos($orderby, '(') !== false) { $orders = explode(',', $orderby); $ob_index = 0; foreach ($orders as $key => $order) { if (!preg_match('%(?P<field>.+)\\s+(?P<direction>DESC|ASC)%i', $order, $order_matches)) { $order_matches = array('field' => $order, 'direction' => ''); } if (strpos($order_matches['field'], '(') !== false) { $ob_index++; $field = 'orderby' . $ob_index; $select_ary[$field] = "{$order_matches['field']} AS {$field}"; $select_distinct[$field] = "{$order_matches['field']} AS {$field}"; $orders[$key] = $field . ' ' . $order_matches['direction']; } } $orderby = implode(', ', $orders); } // Add arbitrary fields to the select clause for sorting and output if (isset($add_select)) { $select_ary = array_merge($select_ary, $add_select); } /** * Turn the requested fields into a comma-separated SELECT field clause */ $select = implode(', ', $select_ary); /** * If a count is requested: * Replace the current fields to select with a COUNT(); * Change the fetch function to 'get_value'; * Remove the ORDER BY since it's useless. * Remove the GROUP BY (tag search added it) */ if (isset($count)) { $select = "COUNT({$count})"; $fetch_fn = 'get_value'; $orderby = ''; $groupby = ''; $having = ''; } // If the month counts are requested, replaced the select clause if (isset($paramset['month_cts'])) { if (isset($paramset['vocabulary'])) { $select = 'MONTH(FROM_UNIXTIME(pubdate)) AS month, YEAR(FROM_UNIXTIME(pubdate)) AS year, COUNT(DISTINCT {posts}.id) AS ct'; } else { $select = 'MONTH(FROM_UNIXTIME(pubdate)) AS month, YEAR(FROM_UNIXTIME(pubdate)) AS year, COUNT(*) AS ct'; } $groupby = 'year, month'; if (!isset($paramarray['orderby'])) { $orderby = 'year, month'; } } // Remove the LIMIT if 'nolimit' // Doing this first should allow OFFSET to work if (isset($nolimit)) { $limit = ''; } // Define the LIMIT and add the OFFSET if it exists if (!empty($limit)) { $limit = " LIMIT {$limit}"; if (isset($offset)) { $limit .= " OFFSET {$offset}"; } } else { $limit = ''; } /* All SQL parts are constructed, on to real business! */ /** * Build the final SQL statement */ $query = ' SELECT DISTINCT ' . $select . ' FROM {posts} ' . "\n " . implode("\n ", $joins) . "\n"; if (count($wheres) > 0) { $query .= ' WHERE (' . implode(" \nOR\n ", $wheres) . ')'; $query .= $master_perm_where == '' ? '' : ' AND (' . $master_perm_where . ')'; } elseif ($master_perm_where != '') { $query .= ' WHERE (' . $master_perm_where . ')'; } $query .= !isset($groupby) || $groupby == '' ? '' : ' GROUP BY ' . $groupby; $query .= !isset($having) || $having == '' ? '' : ' HAVING ' . $having; $query .= ($orderby == '' ? '' : ' ORDER BY ' . $orderby) . $limit; /** * DEBUG: Uncomment the following line to display everything that happens in this function */ //print_R('<pre>'.$query.'</pre>'); //Utils::debug( $paramarray, $fetch_fn, $query, $params ); //Session::notice($query); if ('get_query' == $fetch_fn) { return array($query, $params); } /** * Execute the SQL statement using the PDO extension */ DB::set_fetch_mode(PDO::FETCH_CLASS); DB::set_fetch_class('Post'); $results = DB::$fetch_fn($query, $params, 'Post'); //Utils::debug( $paramarray, $fetch_fn, $query, $params, $results ); //var_dump( $query ); /** * Return the results */ if ('get_results' != $fetch_fn) { // Since a single result was requested, return a single Post object. return $results; } elseif (is_array($results)) { // With multiple results, return a Posts array of Post objects. $c = __CLASS__; $return_value = new $c($results); $return_value->get_param_cache = $paramarray; return $return_value; } }
?> "><?php echo $action['label']; ?> </a></li> <?php } ?> <?php } ?> </ul> </div> <span class="content" ><?php echo MultiByte::substr(strip_tags($post->content), 0, 250); ?> …</span> </div> <?php } } else { ?> <div class="message none"> <p><?php _e('No posts could be found to match the query criteria.'); ?> </p> </div> <?php
/** * The plugin sink for the auth_ajax_wp_import_comments hook. * Responds via authenticated ajax to requests for comment importing. * * @param AjaxHandler $handler The handler that handled the request, contains $_POST info */ public function action_auth_ajax_wp_import_comments($handler) { $valid_fields = array('db_name', 'db_host', 'db_user', 'db_pass', 'db_prefix', 'commentindex', 'category_import', 'utw_import'); $inputs = array_intersect_key($_POST->getArrayCopy(), array_flip($valid_fields)); extract($inputs); $wpdb = $this->wp_connect($db_host, $db_name, $db_user, $db_pass, $db_prefix); if ($wpdb) { if (!DB::in_transaction()) { DB::begin_transaction(); } $commentcount = $wpdb->get_value("SELECT count( comment_ID ) FROM {$db_prefix}comments;"); $min = $commentindex * IMPORT_BATCH + 1; $max = min(($commentindex + 1) * IMPORT_BATCH, $commentcount); echo "<p>Importing comments {$min}-{$max} of {$commentcount}.</p>"; $postinfo = DB::table('postinfo'); $post_info = DB::get_results("SELECT post_id, value FROM {$postinfo} WHERE name= 'wp_id';"); foreach ($post_info as $info) { $post_map[$info->value] = $info->post_id; } $comments = $wpdb->get_results("\n\t\t\t\tSELECT\n\t\t\t\tcomment_content as content,\n\t\t\t\tcomment_author as name,\n\t\t\t\tcomment_author_email as email,\n\t\t\t\tcomment_author_url as url,\n\t\t\t\tINET_ATON( comment_author_IP ) as ip,\n\t\t\t \tcomment_approved as status,\n\t\t\t\tcomment_date as date,\n\t\t\t\tcomment_type as type,\n\t\t\t\tID as wp_post_id\n\t\t\t\tFROM {$db_prefix}comments\n\t\t\t\tINNER JOIN\n\t\t\t\t{$db_prefix}posts on ( {$db_prefix}posts.ID= {$db_prefix}comments.comment_post_ID )\n\t\t\t\tLIMIT {$min}, " . IMPORT_BATCH, array(), 'Comment'); foreach ($comments as $comment) { switch ($comment->type) { case 'pingback': $comment->type = Comment::PINGBACK; break; case 'trackback': $comment->type = Comment::TRACKBACK; break; default: $comment->type = Comment::COMMENT; } $comment->content = MultiByte::convert_encoding($comment->content); $comment->name = MultiByte::convert_encoding($comment->name); $carray = $comment->to_array(); if ($carray['ip'] == '') { $carray['ip'] = 0; } switch ($carray['status']) { case '0': $carray['status'] = Comment::STATUS_UNAPPROVED; break; case '1': $carray['status'] = Comment::STATUS_APPROVED; break; case 'spam': $carray['status'] = Comment::STATUS_SPAM; break; } if (isset($post_map[$carray['wp_post_id']])) { $carray['post_id'] = $post_map[$carray['wp_post_id']]; unset($carray['wp_post_id']); $c = new Comment($carray); //Utils::debug( $c ); try { $c->insert(); } catch (Exception $e) { EventLog::log($e->getMessage(), 'err', null, null, print_r(array($c, $e), 1)); Session::error($e->getMessage()); $errors = Options::get('import_errors'); $errors[] = $e->getMessage(); Options::set('import_errors', $errors); } } } if (DB::in_transaction()) { DB::commit(); } if ($max < $commentcount) { $ajax_url = URL::get('auth_ajax', array('context' => 'wp_import_comments')); $commentindex++; $vars = Utils::addslashes(array('host' => $db_host, 'name' => $db_name, 'user' => $db_user, 'pass' => $db_pass, 'prefix' => $db_prefix)); echo <<<WP_IMPORT_AJAX1 \t\t\t\t\t<script type="text/javascript"> \t\t\t\t\t\$( '#import_progress' ).load( \t\t\t\t\t\t"{$ajax_url}", \t\t\t\t\t\t{ \t\t\t\t\t\t\tdb_host: "{$vars['host']}", \t\t\t\t\t\t\tdb_name: "{$vars['name']}", \t\t\t\t\t\t\tdb_user: "******", \t\t\t\t\t\t\tdb_pass: "******", \t\t\t\t\t\t\tdb_prefix: "{$vars['prefix']}", \t\t\t\t\t\t\tcategory_import: "{$category_import}", \t\t\t\t\t\t\tutw_import: "{$utw_import}", \t\t\t\t\t\t\tcommentindex: {$commentindex} \t\t\t\t\t\t} \t\t\t\t\t ); \t\t\t\t</script> WP_IMPORT_AJAX1; } else { EventLog::log('Import complete from "' . $db_name . '"'); echo '<p>' . _t('Import is complete.') . '</p>'; $errors = Options::get('import_errors'); if (count($errors) > 0) { echo '<p>' . _t('There were errors during import:') . '</p>'; echo '<ul>'; foreach ($errors as $error) { echo '<li>' . $error . '</li>'; } echo '</ul>'; } } } else { EventLog::log(sprintf(_t('Failed to import from "%s"'), $db_name), 'crit'); Session::error($e->getMessage()); echo '<p>' . _t('Failed to connect using the given database connection details.') . '</p>'; } }
/** * Returns a user or users based on supplied parameters. * @todo This class should cache query results! * * @param array $paramarray An associated array of parameters, or a querystring * @return array An array of User objects, or a single User object, depending on request */ public static function get( $paramarray = array() ) { $params = array(); $fns = array( 'get_results', 'get_row', 'get_value' ); $select = ''; // what to select -- by default, everything foreach ( User::default_fields() as $field => $value ) { $select .= ( '' == $select ) ? "{users}.$field" : ", {users}.$field"; } // defaults $orderby = 'id ASC'; $nolimit = true; // Put incoming parameters into the local scope $paramarray = Utils::get_params( $paramarray ); // Transact on possible multiple sets of where information that is to be OR'ed if ( isset( $paramarray['where'] ) && is_array( $paramarray['where'] ) ) { $wheresets = $paramarray['where']; } else { $wheresets = array( array() ); } $wheres = array(); $join = ''; if ( isset( $paramarray['where'] ) && is_string( $paramarray['where'] ) ) { $wheres[] = $paramarray['where']; } else { foreach ( $wheresets as $paramset ) { // safety mechanism to prevent empty queries $where = array(); $paramset = array_merge( (array) $paramarray, (array) $paramset ); $default_fields = User::default_fields(); foreach ( User::default_fields() as $field => $scrap ) { if ( !isset( $paramset[$field] ) ) { continue; } switch ( $field ) { case 'id': if ( !is_numeric( $paramset[$field] ) ) { continue; } default: $where[] = "{$field} = ?"; $params[] = $paramset[$field]; } } if ( isset( $paramset['info'] ) && is_array( $paramset['info'] ) ) { $join .= 'INNER JOIN {userinfo} ON {users}.id = {userinfo}.user_id'; foreach ( $paramset['info'] as $info_name => $info_value ) { $where[] = '{userinfo}.name = ? AND {userinfo}.value = ?'; $params[] = $info_name; $params[] = $info_value; } } if ( isset( $paramset['criteria'] ) ) { if ( isset( $paramset['criteria_fields'] ) ) { // Support 'criteria_fields' => 'author,ip' rather than 'criteria_fields' => array( 'author', 'ip' ) if ( !is_array( $paramset['criteria_fields'] ) && is_string( $paramset['criteria_fields'] ) ) { $paramset['criteria_fields'] = explode( ',', $paramset['criteria_fields'] ); } } else { $paramset['criteria_fields'] = array( 'username' ); } $paramset['criteria_fields'] = array_unique( $paramset['criteria_fields'] ); // this regex matches any unicode letters (\p{L}) or numbers (\p{N}) inside a set of quotes (but strips the quotes) OR not in a set of quotes preg_match_all( '/(?<=")([\p{L}\p{N}]+[^"]*)(?=")|([\p{L}\p{N}]+)/u', $paramset['criteria'], $matches ); $where_search = array(); foreach ( $matches[0] as $word ) { foreach ( $paramset['criteria_fields'] as $criteria_field ) { $where_search[] .= "( LOWER( {users}.$criteria_field ) LIKE ? )"; $params[] = '%' . MultiByte::strtolower( $word ) . '%'; } } if ( count( $where_search ) > 0 ) { $where[] = '(' . implode( " \nOR\n ", $where_search ).')'; } } if ( count( $where ) > 0 ) { $wheres[] = ' (' . implode( ' AND ', $where ) . ') '; } } } // Get any full-query parameters $possible = array( 'fetch_fn', 'count', 'nolimit', 'limit', 'offset' ); foreach ( $possible as $varname ) { if ( isset( $paramarray[$varname] ) ) { $$varname = $paramarray[$varname]; } } if ( isset( $fetch_fn ) ) { if ( ! in_array( $fetch_fn, $fns ) ) { $fetch_fn = $fns[0]; } } else { $fetch_fn = $fns[0]; } // is a count being request? if ( isset( $count ) ) { $select = "COUNT($count)"; $fetch_fn = 'get_value'; $orderby = ''; } if ( isset( $limit ) ) { unset( $nolimit ); $limit = " LIMIT $limit"; if ( isset( $offset ) ) { $limit .= " OFFSET $offset"; } } if ( isset( $nolimit ) ) { $limit = ''; } $query = ' SELECT ' . $select . ' FROM {users} ' . $join; if ( count( $wheres ) > 0 ) { $query .= ' WHERE ' . implode( " \nOR\n ", $wheres ); } $query .= ( ( $orderby == '' ) ? '' : ' ORDER BY ' . $orderby ) . $limit; //Utils::debug($paramarray, $fetch_fn, $query, $params); DB::set_fetch_mode( PDO::FETCH_CLASS ); DB::set_fetch_class( 'User' ); $results = DB::$fetch_fn( $query, $params, 'User' ); if ( 'get_results' != $fetch_fn ) { // return the results return $results; } elseif ( is_array( $results ) ) { $c = __CLASS__; $return_value = new $c( $results ); $return_value->get_param_cache = $paramarray; return $return_value; } }
/** * Receive a Pingback via XMLRPC * @param array $params An array of XMLRPC parameters from the remote call * @return string The success state of the pingback */ public function xmlrpc_pingback__ping( $params ) { try { list( $source_uri, $target_uri )= $params; // This should really be done by an Habari core function $target_parse = InputFilter::parse_url( $target_uri ); $target_stub = $target_parse['path']; $base_url = Site::get_path( 'base', true ); if ( '/' != $base_url) { $target_stub = str_replace( $base_url, '', $target_stub ); } $target_stub = trim( $target_stub, '/' ); if ( strpos( $target_stub, '?' ) !== false ) { list( $target_stub, $query_string )= explode( '?', $target_stub ); } // Can this be used as a target? $target_slug = URL::parse( $target_stub )->named_arg_values['slug']; if ( $target_slug === false ) { throw new XMLRPCException( 33 ); } // Does the target exist? $target_post = Post::get( array( 'slug' => $target_slug ) ); if ( $target_post === false ) { throw new XMLRPCException( 32 ); } // Is comment allowed? if ( $target_post->info->comments_disabled ) { throw new XMLRPCException( 33 ); } // Is this Pingback already registered? if ( Comments::get( array( 'post_id' => $target_post->id, 'url' => $source_uri, 'type' => Comment::PINGBACK ) )->count() > 0 ) { throw new XMLRPCException( 48 ); } // Retrieve source contents try { $rr = new RemoteRequest( $source_uri ); $rr->execute(); if ( ! $rr->executed() ) { throw new XMLRPCException( 16 ); } $source_contents = $rr->get_response_body(); $headers = $rr->get_response_headers(); } catch ( XMLRPCException $e ) { // catch our special type of exception and re-throw it throw $e; } catch ( Exception $e ) { throw new XMLRPCException( -32300 ); } // Encoding is converted into internal encoding. // First, detect the source string's encoding $habari_encoding = strtoupper( MultiByte::hab_encoding() ); $source_encoding = 'Windows-1252'; // Is the charset in the headers? if ( isset( $headers['Content-Type'] ) && strpos( $headers['Content-Type'], 'charset' ) !== false ) { // This regex should be changed to meet the HTTP spec at some point if ( preg_match("/charset[\x09\x0A\x0C\x0D\x20]*=[\x09\x0A\x0C\x0D\x20]*('?)([A-Za-z0-9\-\_]+)\1/i", $headers['Content-Type'], $matches ) ) { $source_encoding = strtoupper( $matches[2] ); } } // Can we tell the charset from the stream itself? else if ( ( $enc = MultiByte::detect_bom_encoding( $source_contents ) ) !== false ) { $source_encoding = $enc; } // Is the charset in a meta tag? else if ( preg_match( "/<meta[^>]+charset[\x09\x0A\x0C\x0D\x20]*=[\x09\x0A\x0C\x0D\x20]*([\"']?)([A-Za-z0-9\-\_]+)\1/i", $source_contents, $matches ) ) { $source_encoding = strtoupper( $matches[2] ); if (in_array($source_encoding, array("UTF-16", "UTF-16BE", "UTF-16LE"))) { $source_encoding = "UTF-8"; } } // Then, convert the string $ret = MultiByte::convert_encoding( $source_contents, $habari_encoding, $source_encoding ); if ( $ret !== false ) { $source_contents = $ret; } // Find the page's title preg_match( '/<title>(.*)<\/title>/is', $source_contents, $matches ); $source_title = $matches[1]; // Find the reciprocal links and their context preg_match( '/<body[^>]*>(.+)<\/body>/is', $source_contents, $matches ); $source_contents_filtered = preg_replace( '/\s{2,}/is', ' ', strip_tags( $matches[1], '<a>' ) ); // Get rid of all the non-recriprocal links $ht = new HTMLTokenizer( trim( $source_contents_filtered ) ); $set = $ht->parse(); $all_links = $set->slice( 'a', array() ); $keep_links = $set->slice( 'a', array( 'href' => $target_uri ) ); $bad_links = array_diff( $all_links, $keep_links ); foreach( $bad_links as $link ) { $link->tokenize_replace( '' ); $set->replace_slice( $link ); } $source_contents_filtered = (string)$set; // Get the excerpt if ( !preg_match( '%.{0,100}?<a[^>]*?href\\s*=\\s*("|\'|)' . $target_uri . '\\1[^>]*?'.'>(.+?)</a>.{0,100}%s', $source_contents_filtered, $source_excerpt ) ) { throw new XMLRPCException( 17 ); } /** Sanitize Data */ $source_excerpt = '…' . InputFilter::filter( $source_excerpt[0] ) . '…'; $source_title = InputFilter::filter($source_title); $source_uri = InputFilter::filter($source_uri); /* Sanitize the URL */ if (!empty($source_uri)) { $parsed = InputFilter::parse_url( $source_uri ); if ( $parsed['is_relative'] ) { // guess if they meant to use an absolute link $parsed = InputFilter::parse_url( 'http://' . $source_uri ); if ( ! $parsed['is_error'] ) { $source_uri = InputFilter::glue_url( $parsed ); } else { // disallow relative URLs $source_uri = ''; } } if ( $parsed['is_pseudo'] || ( $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https' ) ) { // allow only http(s) URLs $source_uri = ''; } else { // reconstruct the URL from the error-tolerant parsing // http:moeffju.net/blog/ -> http://moeffju.net/blog/ $source_uri = InputFilter::glue_url( $parsed ); } } // Add a new pingback comment $pingback = new Comment( array( 'post_id' => $target_post->id, 'name' => $source_title, 'email' => '', 'url' => $source_uri, 'ip' => Utils::get_ip(), 'content' => $source_excerpt, 'status' => Comment::STATUS_UNAPPROVED, 'date' => HabariDateTime::date_create(), 'type' => Comment::PINGBACK, ) ); $pingback->insert(); // Respond to the Pingback return 'The pingback has been registered'; } catch ( XMLRPCException $e ) { $e->output_fault_xml(); } }
/** * Commit $_SESSION data to the database for this user. */ public static function write() { if (!isset(self::$session_id)) { self::create(); } $remote_address = Utils::get_ip(); // not always set, even by real browsers $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; $dowrite = self::changed(); if (isset($_SESSION)) { // get the data from the ArrayObject $data = $_SESSION; } else { $dowrite = false; $data = array(); } // but let a plugin make the final decision. we may want to ignore search spiders, for instance $dowrite = Plugins::filter('session_write', $dowrite, self::$session_id, $data); if ($dowrite) { // DB::update() checks if the record key exists, and inserts if not $record = array('ip' => self::get_subnet($remote_address), 'expires' => DateTime::create()->int + self::$lifetime, 'ua' => MultiByte::substr($user_agent, 0, 255), 'data' => serialize($data)); DB::update(DB::table('sessions'), $record, array('token' => self::$session_id)); } }
/** * @todo TODO must build DOM to really properly remove offending elements * @todo TODO properly filter URLs */ public static function filter_html_elements($str) { $tokenizer = new HTMLTokenizer($str); // tokenize, baby $tokens = $tokenizer->parse(); // filter token stream $filtered = new HTMLTokenSet(); $stack = array(); foreach ($tokens as $node) { switch ($node['type']) { case HTMLTokenizer::NODE_TYPE_TEXT: $node['value'] = html_entity_decode($node['value'], ENT_QUOTES, MultiByte::hab_encoding()); break; case HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN: case HTMLTokenizer::NODE_TYPE_ELEMENT_EMPTY: // is this element allowed at all? if (!in_array(strtolower($node['name']), self::$whitelist_elements)) { if (!in_array(strtolower($node['name']), self::$elements_empty)) { array_push($stack, $node['name']); } //$node = null; //remove the node completely // convert the node to text $node = array('type' => HTMLTokenizer::NODE_TYPE_TEXT, 'name' => '#text', 'value' => HTMLTokenSet::token_to_string($node), 'attrs' => array()); } else { // check attributes foreach ($node['attrs'] as $k => $v) { $attr_ok = false; // if the attribute is in the global whitelist and validates if (array_key_exists(strtolower($k), self::$whitelist_attributes['*']) && self::check_attr_value(strtolower($k), $v, self::$whitelist_attributes['*'][strtolower($k)])) { $attr_ok = true; } // if there is a whitelist for this node and this attribute is in that list and it validates if (array_key_exists(strtolower($node['name']), self::$whitelist_attributes) && array_key_exists(strtolower($k), self::$whitelist_attributes[strtolower($node['name'])]) && self::check_attr_value(strtolower($k), $v, self::$whitelist_attributes[strtolower($node['name'])][strtolower($k)])) { $attr_ok = true; } // if it wasn't in one of the whitelists or failed its check, remove it if ($attr_ok != true) { unset($node['attrs'][$k]); } } } break; case HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE: if (!in_array(strtolower($node['name']), self::$whitelist_elements)) { if (strtolower($temp = array_pop($stack)) !== strtolower($node['name'])) { // something weird happened (Luke, use the DOM!) array_push($stack, $temp); } //$node = null; //convert the node to text $node = array('type' => HTMLTokenizer::NODE_TYPE_TEXT, 'name' => '#text', 'value' => HTMLTokenSet::token_to_string($node), 'attrs' => array()); } break; case HTMLTokenizer::NODE_TYPE_PI: case HTMLTokenizer::NODE_TYPE_COMMENT: case HTMLTokenizer::NODE_TYPE_CDATA_SECTION: case HTMLTokenizer::NODE_TYPE_STATEMENT: default: $node = null; break; } if ($node != null) { $filtered[] = $node; } } // rebuild our output string return preg_replace('#<([^>\\s]+)(?:\\s+[^>]+)?></\\1>#u', '', (string) $filtered); }
/** * Returns a post or posts based on supplied parameters. * @todo <b>THIS CLASS SHOULD CACHE QUERY RESULTS!</b> * * @param array $paramarray An associative array of parameters, or a querystring. * The following keys are supported: * - id => a post id or array of post ids * - not:id => a post id or array of post ids to exclude * - slug => a post slug or array of post slugs * - not:slug => a post slug or array of post slugs to exclude * - user_id => an author id or array of author ids * - content_type => a post content type or array post content types * - not:content_type => a post content type or array post content types to exclude * - status => a post status, an array of post statuses, or 'any' for all statuses * - year => a year of post publication * - month => a month of post publication, ignored if year is not specified * - day => a day of post publication, ignored if month and year are not specified * - before => a timestamp to compare post publication dates * - after => a timestamp to compare post publication dates * - month_cts => return the number of posts published in each month * - criteria => a literal search string to match post content or title * - title => an exact case-insensitive match to a post title * - title_search => a search string that acts only on the post title * - has:info => a post info key or array of post info keys, which should be present * - all:info => a post info key and value pair or array of post info key and value pairs, which should all be present and match * - not:all:info => a post info key and value pair or array of post info key and value pairs, to exclude if all are present and match * - any:info => a post info key and value pair or array of post info key and value pairs, any of which can match * - not:any:info => a post info key and value pair or array of post info key and value pairs, to exclude if any are present and match * - vocabulary => an array describing parameters related to vocabularies attached to posts. This can be one of two forms: * - object-based, in which an array of Term objects are passed * - any => posts associated with any of the terms are returned * - all => posts associated with all of the terms are returned * - not => posts associated with none of the terms are returned * - property-based, in which an array of vocabulary names and associated fields are passed * - vocabulary_name:term => a vocabulary name and term slug pair or array of vocabulary name and term slug pairs, any of which can be associated with the posts * - vocabulary_name:term_display => a vocabulary name and term display pair or array of vocabulary name and term display pairs, any of which can be associated with the posts * - vocabulary_name:not:term => a vocabulary name and term slug pair or array of vocabulary name and term slug pairs, none of which can be associated with the posts * - vocabulary_name:not:term_display => a vocabulary name and term display pair or array of vocabulary name and term display pairs, none of which can be associated with the posts * - vocabulary_name:all:term => a vocabulary name and term slug pair or array of vocabulary name and term slug pairs, all of which must be associated with the posts * - vocabulary_name:all:term_display => a vocabulary name and term display pair or array of vocabulary name and term display pairs, all of which must be associated with the posts * - on_query_built => a closure that accepts a Query as a parameter, allowing a plugin to alter the Query for this request directly * - limit => the maximum number of posts to return, implicitly set for many queries * - nolimit => do not implicitly set limit * - offset => amount by which to offset returned posts, used in conjunction with limit * - page => the 'page' of posts to return when paging, sets the appropriate offset * - count => return the number of posts that would be returned by this request * - orderby => how to order the returned posts * - groupby => columns by which to group the returned posts, for aggregate functions * - having => for selecting posts based on an aggregate function * - where => manipulate the generated WHERE clause. Currently broken, see https://trac.habariproject.org/habari/ticket/1383 * - add_select => an array of clauses to be added to the generated SELECT clause. * - fetch_fn => the function used to fetch data, one of 'get_results', 'get_row', 'get_value', 'get_query' * * Further description of parameters, including usage examples, can be found at * http://wiki.habariproject.org/en/Dev:Retrieving_Posts * * @return Posts|Post|string An array of Post objects, or a single post object, depending on request */ public static function get($paramarray = array()) { static $presets; $select_distinct = array(); // If $paramarray is a string, use it as a Preset if (is_string($paramarray)) { $paramarray = array('preset' => $paramarray); } // If $paramarray is a querystring, convert it to an array $paramarray = Utils::get_params($paramarray); if ($paramarray instanceof \ArrayIterator) { $paramarray = $paramarray->getArrayCopy(); } // If a preset is defined, get the named array and merge it with the provided parameters, // allowing the additional $paramarray settings to override the preset if (isset($paramarray['preset'])) { if (!isset($presets)) { $presets = Plugins::filter('posts_get_all_presets', $presets, $paramarray['preset']); } $paramarray = Posts::merge_presets($paramarray, $presets); } // let plugins alter the param array before we use it. could be useful for modifying search results, etc. $paramarray = Plugins::filter('posts_get_paramarray', $paramarray); $join_params = array(); $params = array(); $fns = array('get_results', 'get_row', 'get_value', 'get_query'); $select_ary = array(); // Default fields to select, everything by default $default_fields = Plugins::filter('post_default_fields', Post::default_fields(), $paramarray); if (isset($paramarray['default_fields'])) { $param_defaults = Utils::single_array($paramarray['default_fields']); $default_fields = array_merge($default_fields, $param_defaults); } foreach ($default_fields as $field => $value) { if (preg_match('/(?:(?P<table>[\\w\\{\\}]+)\\.)?(?P<field>\\w+)(?:(?:\\s+as\\s+)(?P<alias>\\w+))?/i', $field, $fielddata)) { if (empty($fielddata['table'])) { $fielddata['table'] = '{posts}'; } if (empty($fielddata['alias'])) { $fielddata['alias'] = $fielddata['field']; } } $select_ary[$fielddata['alias']] = "{$fielddata['table']}.{$fielddata['field']} AS {$fielddata['alias']}"; $select_distinct[$fielddata['alias']] = "{$fielddata['table']}.{$fielddata['field']}"; } // Define the WHERE sets to process and OR in the final SQL statement if (isset($paramarray['where']) && is_array($paramarray['where'])) { $wheresets = $paramarray['where']; } else { $wheresets = array(array()); } /* Start building the WHERE clauses */ $query = Query::create('{posts}'); $query->select($select_ary); // If the request has a textual WHERE clause, add it to the query then continue the processing of the $wheresets if (isset($paramarray['where']) && is_string($paramarray['where'])) { $query->where()->add($paramarray['where']); } foreach ($wheresets as $paramset) { $where = new QueryWhere(); $paramset = array_merge((array) $paramarray, (array) $paramset); if (isset($paramset['id'])) { $where->in('{posts}.id', $paramset['id'], 'posts_id', 'intval'); } if (isset($paramset['not:id'])) { $where->in('{posts}.id', $paramset['not:id'], 'posts_not_id', 'intval', false); } if (isset($paramset['status']) && !self::empty_param($paramset['status'])) { $where->in('{posts}.status', $paramset['status'], 'posts_status', function ($a) { return Post::status($a); }); } if (isset($paramset['not:status']) && !self::empty_param($paramset['not:status'])) { $where->in('{posts}.status', $paramset['not:status'], 'posts_not_status', function ($a) { return Post::status($a); }, null, false); } if (isset($paramset['content_type']) && !self::empty_param($paramset['content_type'])) { $where->in('{posts}.content_type', $paramset['content_type'], 'posts_content_type', function ($a) { return Post::type($a); }); } if (isset($paramset['not:content_type'])) { $where->in('{posts}.content_type', $paramset['not:content_type'], 'posts_not_content_type', function ($a) { return Post::type($a); }, false); } if (isset($paramset['slug'])) { $where->in('{posts}.slug', $paramset['slug'], 'posts_slug'); } if (isset($paramset['not:slug'])) { $where->in('{posts}.slug', $paramset['not:slug'], 'posts_not_slug', null, false); } if (isset($paramset['user_id']) && 0 !== $paramset['user_id']) { $where->in('{posts}.user_id', $paramset['user_id'], 'posts_user_id', 'intval'); } if (isset($paramset['not:user_id']) && 0 !== $paramset['not:user_id']) { $where->in('{posts}.user_id', $paramset['not:user_id'], 'posts_not_user_id', 'intval', false); } if (isset($paramset['vocabulary'])) { if (is_string($paramset['vocabulary'])) { $paramset['vocabulary'] = Utils::get_params($paramset['vocabulary']); } // parse out the different formats we accept arguments in into a single mutli-dimensional array of goodness $paramset['vocabulary'] = self::vocabulary_params($paramset['vocabulary']); $object_id = Vocabulary::object_type_id('post'); if (isset($paramset['vocabulary']['all'])) { $all = $paramset['vocabulary']['all']; foreach ($all as $vocab => $value) { foreach ($value as $field => $terms) { // we only support these fields to search by if (!in_array($field, array('id', 'term', 'term_display'))) { continue; } $join_group = Query::new_param_name('join'); $query->join('JOIN {object_terms} ' . $join_group . '_ot ON {posts}.id = ' . $join_group . '_ot.object_id', array(), 'term2post_posts_' . $join_group); $query->join('JOIN {terms} ' . $join_group . '_t ON ' . $join_group . '_ot.term_id = ' . $join_group . '_t.id', array(), 'terms_term2post_' . $join_group); $query->join('JOIN {vocabularies} ' . $join_group . '_v ON ' . $join_group . '_t.vocabulary_id = ' . $join_group . '_v.id', array(), 'terms_vocabulary_' . $join_group); $where->in($join_group . '_v.name', $vocab); $where->in($join_group . "_t.{$field}", $terms); $where->in($join_group . '_ot.object_type_id', $object_id); } // this causes no posts to match if combined with 'any' below and should be re-thought... somehow $groupby = implode(',', $select_distinct); $having = 'count(*) = ' . count($terms); // @todo this seems like it's in the wrong place } } if (isset($paramset['vocabulary']['any'])) { $any = $paramset['vocabulary']['any']; $orwhere = new QueryWhere('OR'); foreach ($any as $vocab => $value) { foreach ($value as $field => $terms) { $andwhere = new QueryWhere(); // we only support these fields to search by if (!in_array($field, array('id', 'term', 'term_display'))) { continue; } $join_group = Query::new_param_name('join'); $query->join('JOIN {object_terms} ' . $join_group . '_ot ON {posts}.id = ' . $join_group . '_ot.object_id', array(), 'term2post_posts_' . $join_group); $query->join('JOIN {terms} ' . $join_group . '_t ON ' . $join_group . '_ot.term_id = ' . $join_group . '_t.id', array(), 'terms_term2post_' . $join_group); $query->join('JOIN {vocabularies} ' . $join_group . '_v ON ' . $join_group . '_t.vocabulary_id = ' . $join_group . '_v.id', array(), 'terms_vocabulary_' . $join_group); $andwhere->in($join_group . '_v.name', $vocab); $andwhere->in($join_group . "_t.{$field}", $terms); $andwhere->in($join_group . '_ot.object_type_id', $object_id); } $orwhere->add($andwhere); // @todo this seems like it's in the wrong place } $where->add($orwhere); } if (isset($paramset['vocabulary']['not'])) { $not = $paramset['vocabulary']['not']; foreach ($not as $vocab => $value) { foreach ($value as $field => $terms) { // we only support these fields to search by if (!in_array($field, array('id', 'term', 'term_display'))) { continue; } $subquery_alias = Query::new_param_name('subquery'); $subquery = Query::create('{object_terms}')->select('object_id'); $subquery->join('JOIN {terms} ON {terms}.id = {object_terms}.term_id'); $subquery->join('JOIN {vocabularies} ON {terms}.vocabulary_id = {vocabularies}.id'); $subquery->where()->in("{terms}.{$field}", $terms); $subquery->where()->in('{object_terms}.object_type_id', $object_id); $subquery->where()->in('{vocabularies}.name', $vocab); $query->join('LEFT JOIN (' . $subquery->get() . ') ' . $subquery_alias . ' ON ' . $subquery_alias . '.object_id = {posts}.id', $subquery->params(), $subquery_alias); $where->add('COALESCE(' . $subquery_alias . '.object_id, 0) = 0'); } } } } if (isset($paramset['criteria'])) { // this regex matches any unicode letters (\p{L}) or numbers (\p{N}) inside a set of quotes (but strips the quotes) OR not in a set of quotes preg_match_all('/(?<=")([\\p{L}\\p{N}]+[^"]*)(?=")|([\\p{L}\\p{N}]+)/u', $paramset['criteria'], $matches); foreach ($matches[0] as $word) { $crit_placeholder = $query->new_param_name('criteria'); $where->add("( LOWER( {posts}.title ) LIKE :{$crit_placeholder} OR LOWER( {posts}.content ) LIKE :{$crit_placeholder})", array($crit_placeholder => '%' . MultiByte::strtolower($word) . '%')); } } if (isset($paramset['title'])) { $where->add("LOWER( {posts}.title ) LIKE :title_match", array('title_match' => MultiByte::strtolower($paramset['title']))); } if (isset($paramset['title_search'])) { // this regex matches any unicode letters (\p{L}) or numbers (\p{N}) inside a set of quotes (but strips the quotes) OR not in a set of quotes preg_match_all('/(?<=")([\\p{L}\\p{N}]+[^"]*)(?=")|([\\p{L}\\p{N}]+)/u', $paramset['title_search'], $matches); foreach ($matches[0] as $word) { $crit_placeholder = $query->new_param_name('title_search'); $where->add("LOWER( {posts}.title ) LIKE :{$crit_placeholder}", array($crit_placeholder => '%' . MultiByte::strtolower($word) . '%')); } } // Handle field queries on posts and joined tables foreach ($select_ary as $field => $aliasing) { if (in_array($field, array('id', 'title', 'slug', 'status', 'content_type', 'user_id'))) { // skip fields that we're handling a different way continue; } if (isset($paramset[$field])) { if (is_callable($paramset[$field])) { $paramset[$field]($where, $paramset); } else { $where->in($field, $paramset[$field], 'posts_field_' . $field); } } } //Done if (isset($paramset['all:info']) || isset($paramset['info'])) { // merge the two possibile calls together $infos = array_merge(isset($paramset['all:info']) ? $paramset['all:info'] : array(), isset($paramset['info']) ? $paramset['info'] : array()); if (Utils::is_traversable($infos)) { $pi_count = 0; foreach ($infos as $info_key => $info_value) { $pi_count++; $infokey_field = Query::new_param_name('info_key'); $infovalue_field = Query::new_param_name('info_value'); $query->join("LEFT JOIN {postinfo} ipi{$pi_count} ON {posts}.id = ipi{$pi_count}.post_id AND ipi{$pi_count}.name = :{$infokey_field} AND ipi{$pi_count}.value = :{$infovalue_field}", array($infokey_field => $info_key, $infovalue_field => $info_value), 'all_info_' . $info_key); $where->add("ipi{$pi_count}.name <> ''"); $query->select(array("info_{$info_key}_value" => "ipi{$pi_count}.value AS info_{$info_key}_value")); $select_distinct["info_{$info_key}_value"] = "info_{$info_key}_value"; } } } //Done if (isset($paramset['any:info'])) { if (Utils::is_traversable($paramset['any:info'])) { $pi_count = 0; $orwhere = new QueryWhere('OR'); foreach ($paramset['any:info'] as $info_key => $info_value) { $pi_count++; if (is_array($info_value)) { $infokey_field = Query::new_param_name('info_key'); $inwhere = new QueryWhere(''); $inwhere->in("aipi{$pi_count}.value", $info_value); $query->join("LEFT JOIN {postinfo} aipi{$pi_count} ON {posts}.id = aipi{$pi_count}.post_id AND aipi{$pi_count}.name = :{$infokey_field} AND " . $inwhere->get(), array_merge(array($info_key), $inwhere->params()), 'any_info_' . $info_key); } else { $infokey_field = Query::new_param_name('info_key'); $infovalue_field = Query::new_param_name('info_value'); $query->join("LEFT JOIN {postinfo} aipi{$pi_count} ON {posts}.id = aipi{$pi_count}.post_id AND aipi{$pi_count}.name = :{$infokey_field} AND aipi{$pi_count}.value = :{$infovalue_field}", array($infokey_field => $info_key, $infovalue_field => $info_value), 'any_info_' . $info_key); } $orwhere->add("aipi{$pi_count}.name <> ''"); $query->select(array("info_{$info_key}_value" => "aipi{$pi_count}.value AS info_{$info_key}_value")); $select_distinct["info_{$info_key}_value"] = "info_{$info_key}_value"; } $where->add('(' . $orwhere->get() . ')'); } } // Done if (isset($paramset['has:info'])) { $has_info = Utils::single_array($paramset['has:info']); $pi_count = 0; $orwhere = new QueryWhere('OR'); foreach ($has_info as $info_name) { $infoname_field = Query::new_param_name('info_name'); $pi_count++; $query->join("LEFT JOIN {postinfo} hipi{$pi_count} ON {posts}.id = hipi{$pi_count}.post_id AND hipi{$pi_count}.name = :{$infoname_field}", array($infoname_field => $info_name), 'has_info_' . $info_name); $orwhere->add("hipi{$pi_count}.name <> ''"); $query->select(array("info_{$info_name}_value" => "hipi{$pi_count}.value AS info_{$info_name}_value")); $select_distinct["info_{$info_name}_value"] = "info_{$info_name}_value"; } $where->add('(' . $orwhere->get() . ')'); } //Done if (isset($paramset['not:all:info']) || isset($paramset['not:info'])) { // merge the two possible calls together $infos = array_merge(isset($paramset['not:all:info']) ? $paramset['not:all:info'] : array(), isset($paramset['not:info']) ? $paramset['not:info'] : array()); if (Utils::is_traversable($infos)) { $orwhere = new QueryWhere('OR'); foreach ($infos as $info_key => $info_value) { $andwhere = new QueryWhere(); $andwhere->in('{postinfo}.name', $info_key); $andwhere->in('{postinfo}.value', $info_value); $orwhere->add($andwhere); } // see that hard-coded number in having()? sqlite wets itself if we use a bound parameter... don't change that $subquery = Query::create('{postinfo}')->select('{postinfo}.post_id')->groupby('post_id')->having('COUNT(*) = ' . count($infos)); $subquery->where()->add($orwhere); $where->in('{posts}.id', $subquery, 'posts_not_all_info_query', null, false); } } //Tested. Test fails with original code if (isset($paramset['not:any:info'])) { if (Utils::is_traversable($paramset['not:any:info'])) { $subquery = Query::create('{postinfo}')->select('post_id'); foreach ($paramset['not:any:info'] as $info_key => $info_value) { $infokey_field = $query->new_param_name('info_key'); $infovalue_field = $query->new_param_name('info_value'); // $subquery->where()->add(" ({postinfo}.name = :{$infokey_field} AND {postinfo}.value = :{$infovalue_field} ) ", array($infokey_field => $info_key, $infovalue_field => $info_value)); $subquery->where('OR')->add(" ({postinfo}.name = :{$infokey_field} AND {postinfo}.value = :{$infovalue_field} ) ", array($infokey_field => $info_key, $infovalue_field => $info_value)); } $where->in('{posts}.id', $subquery, 'posts_not_any_info', null, false); } } /** * Build the statement needed to filter by pubdate: * If we've got the day, then get the date; * If we've got the month, but no date, get the month; * If we've only got the year, get the whole year. */ if (isset($paramset['day']) && isset($paramset['month']) && isset($paramset['year'])) { $start_date = sprintf('%d-%02d-%02d', $paramset['year'], $paramset['month'], $paramset['day']); $start_date = DateTime::create($start_date); $where->add('pubdate BETWEEN :start_date AND :end_date', array('start_date' => $start_date->sql, 'end_date' => $start_date->modify('+1 day -1 second')->sql)); } elseif (isset($paramset['month']) && isset($paramset['year'])) { $start_date = sprintf('%d-%02d-%02d', $paramset['year'], $paramset['month'], 1); $start_date = DateTime::create($start_date); $where->add('pubdate BETWEEN :start_date AND :end_date', array('start_date' => $start_date->sql, 'end_date' => $start_date->modify('+1 month -1 second')->sql)); } elseif (isset($paramset['year'])) { $start_date = sprintf('%d-%02d-%02d', $paramset['year'], 1, 1); $start_date = DateTime::create($start_date); $where->add('pubdate BETWEEN :start_date AND :end_date', array('start_date' => $start_date->sql, 'end_date' => $start_date->modify('+1 year -1 second')->sql)); } if (isset($paramset['after'])) { $where->add('pubdate > :after_date', array('after_date' => DateTime::create($paramset['after'])->sql)); } if (isset($paramset['before'])) { $where->add('pubdate < :before_date', array('before_date' => DateTime::create($paramset['before'])->sql)); } // Concatenate the WHERE clauses $query->where()->add($where); } if (isset($paramset['post_join'])) { $post_joins = Utils::single_array($paramset['post_join']); foreach ($post_joins as $post_join) { if (preg_match('#^(\\S+)(?:\\s+as)?\\s+(\\S+)$#i', $post_join, $matches)) { $query->join("LEFT JOIN {$matches[1]} {$matches[2]} ON {$matches[2]}.post_id = {posts}.id "); } else { $query->join("LEFT JOIN {$post_join} ON {$post_join}.post_id = {posts}.id "); } } } // Only show posts to which the current user has permission if (isset($paramset['ignore_permissions'])) { $master_perm_where = new QueryWhere(); // Set up the merge params $merge_params = array($join_params, $params); $params = call_user_func_array('array_merge', $merge_params); } else { $master_perm_where = new QueryWhere(); // This set of wheres will be used to generate a list of post_ids that this user can read $perm_where = new QueryWhere('OR'); $perm_where_denied = new QueryWhere('AND'); // Get the tokens that this user is granted or denied access to read $read_tokens = isset($paramset['read_tokens']) ? $paramset['read_tokens'] : ACL::user_tokens(User::identify(), 'read', true); $deny_tokens = isset($paramset['deny_tokens']) ? $paramset['deny_tokens'] : ACL::user_tokens(User::identify(), 'deny', true); // If a user can read any post type, let him if (User::identify()->can('post_any', 'read')) { $perm_where->add('(1=1)'); } else { // If a user can read his own posts, let him if (User::identify()->can('own_posts', 'read')) { $perm_where->add('{posts}.user_id = :current_user_id', array('current_user_id' => User::identify()->id)); } // If a user can read specific post types, let him $permitted_post_types = array(); foreach (Post::list_active_post_types() as $name => $posttype) { if (User::identify()->can('post_' . Utils::slugify($name), 'read')) { $permitted_post_types[] = $posttype; } } if (count($permitted_post_types) > 0) { $perm_where->in('{posts}.content_type', $permitted_post_types, 'posts_permitted_types', 'intval'); } // If a user can read posts with specific tokens, let him if (count($read_tokens) > 0) { $query->join('LEFT JOIN {post_tokens} pt_allowed ON {posts}.id= pt_allowed.post_id AND pt_allowed.token_id IN (' . implode(',', $read_tokens) . ')', array(), 'post_tokens__allowed'); $perm_where->add('pt_allowed.post_id IS NOT NULL', array(), 'perms_join_not_null'); } // If a user has access to read other users' unpublished posts, let him if (User::identify()->can('post_unpublished', 'read')) { $perm_where->add('({posts}.status <> :status_published AND {posts}.user_id <> :current_user_id)', array('current_user_id' => User::identify()->id, 'status_published' => Post::status('published'))); } } // If a user is denied access to all posts, do so if (User::identify()->cannot('post_any')) { $perm_where_denied->add('(1=0)'); } else { // If a user is denied read access to specific post types, deny him $denied_post_types = array(); foreach (Post::list_active_post_types() as $name => $posttype) { if (User::identify()->cannot('post_' . Utils::slugify($name))) { $denied_post_types[] = $posttype; } } if (count($denied_post_types) > 0) { $perm_where_denied->in('{posts}.content_type', $denied_post_types, 'posts_denied_types', 'intval', false); } // If a user is denied read access to posts with specific tokens, deny it if (count($deny_tokens) > 0) { $query->join('LEFT JOIN {post_tokens} pt_denied ON {posts}.id= pt_denied.post_id AND pt_denied.token_id IN (' . implode(',', $deny_tokens) . ')', array(), 'post_tokens__denied'); $perm_where_denied->add('pt_denied.post_id IS NULL', array(), 'perms_join_null'); } // If a user is denied access to read other users' unpublished posts, deny it if (User::identify()->cannot('post_unpublished')) { $perm_where_denied->add('({posts}.status = :status_published OR {posts}.user_id = :current_user_id)', array('current_user_id' => User::identify()->id, 'status_published' => Post::status('published'))); } } Plugins::act('post_get_perm_where', $perm_where, $paramarray); Plugins::act('post_get_perm_where_denied', $perm_where_denied, $paramarray); // If there are granted permissions to check, add them to the where clause if ($perm_where->count() == 0 && !$query->joined('post_tokens__allowed')) { $master_perm_where->add('(1=0)', array(), 'perms_granted'); } else { $master_perm_where->add($perm_where, array(), 'perms_granted'); } // If there are denied permissions to check, add them to the where clause if ($perm_where_denied->count() > 0 || $query->joined('post_tokens__denied')) { $master_perm_where->add($perm_where_denied, array(), 'perms_denied'); } } $query->where()->add($master_perm_where, array(), 'master_perm_where'); // Extract the remaining parameters which will be used onwards // For example: page number, fetch function, limit $paramarray = new SuperGlobal($paramarray); $extract = $paramarray->filter_keys('page', 'fetch_fn', 'count', 'orderby', 'groupby', 'limit', 'offset', 'nolimit', 'having', 'add_select'); foreach ($extract as $key => $value) { ${$key} = $value; } // Calculate the OFFSET based on the page number. Requires a limit. if (isset($page) && is_numeric($page) && !isset($paramset['offset']) && isset($limit)) { $offset = (intval($page) - 1) * intval($limit); } /** * Determine which fetch function to use: * If it is specified, make sure it is valid (based on the $fns array defined at the beginning of this function); * Else, use 'get_results' which will return a Posts array of Post objects. */ if (isset($fetch_fn)) { if (!in_array($fetch_fn, $fns)) { $fetch_fn = $fns[0]; } } else { $fetch_fn = $fns[0]; } // Add arbitrary fields to the select clause for sorting and output if (isset($add_select)) { $query->select($add_select); } /** * If a count is requested: * Replace the current fields to select with a COUNT(); * Change the fetch function to 'get_value'; * Remove the ORDER BY since it's useless. * Remove the GROUP BY (tag search added it) */ if (isset($count)) { $query->set_select("COUNT({$count})"); $fetch_fn = isset($paramarray['fetch_fn']) ? $fetch_fn : 'get_value'; $orderby = null; $groupby = null; $having = null; } // If the month counts are requested, replaced the select clause if (isset($paramset['month_cts'])) { if (isset($paramset['vocabulary'])) { $query->set_select('MONTH(FROM_UNIXTIME(pubdate)) AS month, YEAR(FROM_UNIXTIME(pubdate)) AS year, COUNT(DISTINCT {posts}.id) AS ct'); } else { $query->set_select('MONTH(FROM_UNIXTIME(pubdate)) AS month, YEAR(FROM_UNIXTIME(pubdate)) AS year, COUNT(*) AS ct'); } $groupby = 'year, month'; if (!isset($paramarray['orderby'])) { $orderby = 'year, month'; } } // Remove the LIMIT if 'nolimit' // Doing this first should allow OFFSET to work if (isset($nolimit)) { $limit = null; } // Define the LIMIT, OFFSET, ORDER BY, GROUP BY if they exist if (isset($limit)) { $query->limit($limit); } if (isset($offset)) { $query->offset($offset); } if (isset($orderby)) { $query->orderby($orderby); } if (isset($groupby)) { $query->groupby($groupby); } if (isset($having)) { $query->having($having); } if (isset($paramarray['on_query_built'])) { foreach (Utils::single_array($paramarray['on_query_built']) as $built) { $built($query); } } Plugins::act('posts_get_query', $query, $paramarray); /* All SQL parts are constructed, on to real business! */ /** * DEBUG: Uncomment the following line to display everything that happens in this function */ //print_R('<pre>'.$query.'</pre>'); //Utils::debug( $paramarray, $fetch_fn, $query, $params ); //Session::notice($query); if ('get_query' == $fetch_fn) { return array($query->get(), $query->params()); } /** * Execute the SQL statement using the PDO extension */ DB::set_fetch_mode(\PDO::FETCH_CLASS); $fetch_class = 'Post'; if (isset($paramarray['fetch_class'])) { $fetch_class = $paramarray['fetch_class']; } DB::set_fetch_class($fetch_class); $results = DB::$fetch_fn($query->get(), $query->params(), $fetch_class); //Utils::debug($results, $query->get(), $query->params()); //Utils::debug( $paramarray, $fetch_fn, $query->get(), $query->params(), $results ); //var_dump( $query ); /** * Return the results */ if ('get_results' != $fetch_fn) { // Since a single result was requested, return a single Post object. return $results; } elseif (is_array($results)) { // With multiple results, return a Posts array of Post objects. $c = __CLASS__; $return_value = new $c($results); $return_value->get_param_cache = $paramarray; return $return_value; } }
/** * returns the integer value of the specified post type, or false * @param mixed a post type name or number * @return mixed an integer or boolean false */ public static function type( $name ) { $types = Post::list_active_post_types(); if ( is_numeric( $name ) && ( false !== in_array( $name, $types ) ) ) { return $name; } if ( isset( $types[ MultiByte::strtolower( $name ) ] ) ) { return $types[ MultiByte::strtolower( $name ) ]; } return false; }
/** * Parse tag parameters from a URL string * * @param String $tags The URL parameter string * * @return Array. Associative array of included and excluded tags */ public static function parse_url_tags($tags, $objectify = false) { $tags = explode(' ', $tags); $exclude_tag = array(); $include_tag = array(); foreach ($tags as $tag) { if (MultiByte::substr($tag, 0, 1) == '-') { $tag = MultiByte::substr($tag, 1); $exclude_tag[] = $objectify ? Tags::get_one(Utils::slugify($tag)) : Utils::slugify($tag); } else { $include_tag[] = $objectify ? Tags::get_one(Utils::slugify($tag)) : Utils::slugify($tag); } } return compact('include_tag', 'exclude_tag'); }
</p> <?php } ?> <?php Plugins::act('comment_info', $comment); ?> <p class="comment-type"><?php echo Plugins::filter('comment_type_display', $comment->typename, 'singular'); ?> </p> </div> <span class="content pct75"><?php if (MultiByte::valid_data($comment->content)) { echo nl2br(Utils::htmlspecialchars($comment->content)); } else { _e('this post contains text in an invalid encoding'); } ?> </span> </div> </div> <?php } } else { ?> <div class="message none"> <p><?php
/** * Handles AJAX requests from media silos. */ public function ajax_media($handler_vars) { Utils::check_request_method(array('POST')); $path = $handler_vars['path']; $rpath = $path; $silo = Media::get_silo($rpath, true); // get_silo sets $rpath by reference to the path inside the silo $assets = Media::dir($path); $output = array('ok' => 1, 'dirs' => array(), 'files' => array(), 'path' => $path); foreach ($assets as $asset) { if ($asset->is_dir) { $output['dirs'][$asset->basename] = $asset->get_props(); } else { $output['files'][$asset->basename] = $asset->get_props(); } } $rootpath = MultiByte::strpos($path, '/') !== false ? MultiByte::substr($path, 0, MultiByte::strpos($path, '/')) : $path; $controls = array('root' => '<a href="#" onclick="habari.media.fullReload();habari.media.showdir(\'' . $rootpath . '\');return false;">' . _t('Root') . '</a>'); $controls = Plugins::filter('media_controls', $controls, $silo, $rpath, ''); $controls_out = ''; foreach ($controls as $k => $v) { if (is_numeric($k)) { $controls_out .= "<li>{$v}</li>"; } else { $controls_out .= "<li class=\"{$k}\">{$v}</li>"; } } $output['controls'] = $controls_out; echo json_encode($output); }