/** * @access public * @since 8.5.15 * * @global wpdb $wpdb * * @param array $atts * * @return string */ public static function where($atts = array()) { global $wpdb; $defaults = array('table' => CN_ENTRY_TABLE, 'field' => '', 'value' => '', 'format' => '%s', 'relation' => 'AND', 'delimiter' => '|'); $atts = cnSanitize::args($atts, $defaults); $where = ''; $atts['relation'] = in_array($atts['relation'], array('AND', 'OR')) ? strtoupper($atts['relation']) : 'AND'; if (0 < strlen($atts['field']) && 0 < count($atts['value'])) { $value = cnFunction::parseStringList($atts['value'], $atts['delimiter']); $count = count($value); if (1 < $count) { $where = $atts['relation'] . ' ' . $atts['table'] . '.' . $atts['field'] . ' ' . self::in($value, $atts['format']); } elseif (1 == $count) { $where = $wpdb->prepare($atts['relation'] . ' ' . $atts['table'] . '.' . $atts['field'] . ' = ' . $atts['format'], $value); } } return $where; }
/** * Display a message box above the search results with information * about the current query and the option (a button) to clear results. * * @access public * @since 0.8 * @static * @param array $atts The shortcode $atts array. * @param array $results The cnRetrieve query results. * @param cnTemplate|null $template An instance of the cnTemplate object. * * @return string */ public static function searchingMessage($atts = array(), $results = array(), $template = NULL) { // Check whether or not the category description should be displayed or not. if (!cnSettingsAPI::get('connections', 'connections_display_results', 'search_message')) { return ''; } $html = ''; $messages = array(); $defaults = array('header' => '', 'header_tag' => 'h3', 'container_tag' => 'ul', 'item_tag' => 'li', 'before' => '', 'before-item' => '', 'after-item' => '', 'after' => '', 'return' => FALSE); $atts = wp_parse_args($atts, apply_filters('cn_search_results_atts', $defaults)); // Get the directory home page ID. $homeID = $atts['force_home'] ? cnSettingsAPI::get('connections', 'connections_home_page', 'page_id') : $atts['home_id']; //$addAction = cnSettingsAPI::get( 'connections', 'connections_home_page', 'page_id' ) != $atts['home_id'] ? TRUE : FALSE; // The base post permalink is required, do not filter the permalink thru cnSEO. if (!is_admin()) { cnSEO::doFilterPermalink(FALSE); } $permalink = get_permalink($homeID); $permalink = apply_filters('cn_permalink', $permalink, $atts); // Re-enable the filter. if (!is_admin()) { cnSEO::doFilterPermalink(); } // Store the query vars $queryVars = array(); $queryVars['cn-s'] = cnQuery::getVar('cn-s') ? esc_html(cnQuery::getVar('cn-s')) : FALSE; $queryVars['cn-char'] = cnQuery::getVar('cn-char') ? esc_html(urldecode(cnQuery::getVar('cn-char'))) : FALSE; $queryVars['cn-cat'] = cnQuery::getVar('cn-cat') ? cnQuery::getVar('cn-cat') : FALSE; $queryVars['cn-organization'] = cnQuery::getVar('cn-organization') ? esc_html(urldecode(cnQuery::getVar('cn-organization'))) : FALSE; $queryVars['cn-department'] = cnQuery::getVar('cn-department') ? esc_html(urldecode(cnQuery::getVar('cn-department'))) : FALSE; $queryVars['cn-locality'] = cnQuery::getVar('cn-locality') ? esc_html(urldecode(cnQuery::getVar('cn-locality'))) : FALSE; $queryVars['cn-region'] = cnQuery::getVar('cn-region') ? esc_html(urldecode(cnQuery::getVar('cn-region'))) : FALSE; $queryVars['cn-postal-code'] = cnQuery::getVar('cn-postal-code') ? esc_html(urldecode(cnQuery::getVar('cn-postal-code'))) : FALSE; $queryVars['cn-country'] = cnQuery::getVar('cn-country') ? esc_html(urldecode(cnQuery::getVar('cn-country'))) : FALSE; // if ( cnQuery::getVar('cn-near-coord') ) $queryVars['cn-near-coord'] = cnQuery::getVar('cn-near-coord'); // if ( cnQuery::getVar('cn-radius') ) $queryVars['cn-radius'] = cnQuery::getVar('cn-radius'); // if ( cnQuery::getVar('cn-unit') ) $queryVars['cn-unit'] = cnQuery::getVar('cn-unit'); $messages = apply_filters('cn_search_results_messages-before', $messages, $atts, $results, $template); if ($queryVars['cn-cat']) { $categoryID = $queryVars['cn-cat']; $terms = array(); // Since the `cn-cat` query var can be an array, we'll only add the category slug // template name when querying a single category. if (is_array($categoryID)) { foreach ($categoryID as $id) { $term = cnTerm::getBy('id', $id, 'category'); $terms[] = esc_html($term->name); } } else { $term = cnTerm::getBy('id', $categoryID, 'category'); $terms[] = esc_html($term->name); } $messages['cn-cat'] = sprintf(__('You are searching within category(ies): %s', 'connections'), implode(', ', $terms)); } if ($queryVars['cn-s']) { // If value is a string, string the white space and covert to an array. if (!is_array($queryVars['cn-s'])) { $originalString = array($queryVars['cn-s']); $queryVars['cn-s'] = cnFunction::parseStringList($queryVars['cn-s'], '\\s'); $queryVars['cn-s'] = array_merge($originalString, $queryVars['cn-s']); $queryVars['cn-s'] = array_unique($queryVars['cn-s']); } // Trim any white space from around the terms in the array. array_walk($queryVars['cn-s'], 'trim'); $messages['cn-s'] = sprintf(__('You are searching for the keyword(s): %s', 'connections'), esc_html(implode(', ', $queryVars['cn-s']))); } if ($queryVars['cn-char']) { $messages['cn-char'] = sprintf(__('The results are being filtered by the character: %s', 'connections'), $queryVars['cn-char']); } if ($queryVars['cn-organization']) { $messages['cn-organization'] = sprintf(__('The results are being filtered by the organization: %s', 'connections'), $queryVars['cn-organization']); } if ($queryVars['cn-department']) { $messages['cn-department'] = sprintf(__('The results are being filtered by the department: %s', 'connections'), $queryVars['cn-department']); } if ($queryVars['cn-locality']) { $messages['cn-locality'] = sprintf(__('The results are being filtered by the locality: %s', 'connections'), $queryVars['cn-locality']); } if ($queryVars['cn-region']) { $messages['cn-region'] = sprintf(__('The results are being filtered by the region: %s', 'connections'), $queryVars['cn-region']); } if ($queryVars['cn-postal-code']) { $messages['cn-postal-code'] = sprintf(__('The results are being filtered by the postal code: %s', 'connections'), $queryVars['cn-postal-code']); } if ($queryVars['cn-country']) { $messages['cn-country'] = sprintf(__('The results are being filtered by the country: %s', 'connections'), $queryVars['cn-country']); } $messages = apply_filters('cn_search_results_messages-after', $messages, $atts, $results, $template); $messages = apply_filters('cn_search_results_messages', $messages, $atts, $results, $template); if (!empty($messages)) { if (0 < strlen($atts['header'])) { $header = sprintf('<%1$s class="cn-search-message-header">%2$s</%1$s>', $atts['header_tag'], esc_html($atts['header'])); } else { $header = ''; } // Render the HTML <li> items. foreach ($messages as $key => $message) { $html .= sprintf(PHP_EOL . "\t" . '<%2$s class="cn-search-message-list-item" id="cn-search-message-list-item-%3$s">%1$s%4$s%5$s</%2$s>', $atts['before-item'], $atts['item_tag'], esc_attr($key), wp_kses_post($message), $atts['after-item']); } // Wrap the <li> items in a <ul>. $html = sprintf('<%1$s class="cn-search-message-list">%2$s' . PHP_EOL . '</%1$s>', $atts['container_tag'], $html); // Add the clear search button. $html .= sprintf('<div id="cn-clear-search"><a class="button btn" id="cn-clear-search-button" href="%1$s">%2$s</a></div>' . PHP_EOL, esc_url($permalink), __('Clear Search', 'connections')); // Wrap it all in a <div>. $html = '<div id="cn-search-messages">' . $header . $html . '</div>' . PHP_EOL; } $html = $atts['before'] . $html . $atts['after'] . PHP_EOL; return self::echoOrReturn($atts['return'], $html); }
/** * Returns an indexed array of objects the addresses per the defined options. * * @param $atts { * @type string $fields The fields to return. * Default: all * Accepts: all, ids, locality, regions, postal-code, country * @type int $id The entry ID in which to retrieve the addresses for. * @type bool $preferred Whether or not to return only the preferred address. * Default: false * @type array|string $type The address types to return. * Default: array() which will return all registered address types. * Accepts: home, work, school, other and any other registered types. * @type array|string $city Return address in the defined cities. * @type array|string $state Return address in the defined states. * @type array|string $country Return address in the defined countries. * @type array $coordinates { * Return the addresses at the specific coordinates. * @type float $latitude * @type float $longitude * } * } * * @return array */ public function addresses($atts = array()) { /** @var wpdb $wpdb */ global $wpdb; $where = array('WHERE 1=1'); /* * // START -- Set the default attributes array. \\ */ $defaults = array('fields' => 'all', 'id' => NULL, 'preferred' => FALSE, 'type' => array(), 'city' => array(), 'state' => array(), 'zipcode' => array(), 'country' => array(), 'coordinates' => array(), 'limit' => NULL); $atts = cnSanitize::args($atts, $defaults); /* * // END -- Set the default attributes array if not supplied. \\ */ /** * @var int $id * @var bool $preferred * @var array|string $type * @var array|string $city * @var array|string $state * @var array|string $zipcode * @var array|string $country * @var array $coordinates * @var null|int $limit */ extract($atts); /* * Convert these to values to an array if they were supplied as a comma delimited string */ /** @var array $type */ cnFunction::parseStringList($type); /** @var array $city */ cnFunction::parseStringList($city); /** @var array $state */ cnFunction::parseStringList($state); /** @var array $zipcode */ cnFunction::parseStringList($zipcode); /** @var array $country */ cnFunction::parseStringList($country); switch ($atts['fields']) { case 'ids': $select = array('a.id', 'a.entry_id'); break; case 'locality': $select = array('a.city'); break; case 'region': $select = array('a.state'); break; case 'postal-code': $select = array('a.zipcode'); break; case 'country': $select = array('a.country'); break; default: $select = array('a.*'); } if (!empty($id)) { $where[] = $wpdb->prepare('AND `entry_id` = %d', $id); } if (!empty($preferred)) { $where[] = $wpdb->prepare('AND `preferred` = %d', (bool) $preferred); } if (!empty($type)) { $where[] = $wpdb->prepare('AND `type` IN (' . cnFormatting::prepareINPlaceholders($type) . ')', $type); } if (!empty($city)) { $where[] = $wpdb->prepare('AND `city` IN (' . cnFormatting::prepareINPlaceholders($city) . ')', $city); } if (!empty($state)) { $where[] = $wpdb->prepare('AND `state` IN (' . cnFormatting::prepareINPlaceholders($state) . ')', $state); } if (!empty($zipcode)) { $where[] = $wpdb->prepare('AND `zipcode` IN (' . cnFormatting::prepareINPlaceholders($zipcode) . ')', $zipcode); } if (!empty($country)) { $where[] = $wpdb->prepare('AND `country` IN (' . cnFormatting::prepareINPlaceholders($country) . ')', $country); } if (!empty($coordinates)) { if (!empty($coordinates['latitude']) && !empty($coordinates['longitude'])) { $where[] = $wpdb->prepare('AND `latitude` = %f', $coordinates['latitude']); $where[] = $wpdb->prepare('AND `longitude` = %f', $coordinates['longitude']); } } // Limit the characters that are queried based on if the current user can view public, private or unlisted entries. $where = self::setQueryVisibility($where, array('table' => 'a')); $limit = is_null($atts['limit']) ? '' : sprintf(' LIMIT %d', $atts['limit']); $sql = sprintf('SELECT %1$s FROM %2$s AS a %3$s ORDER BY `order`%4$s', implode(', ', $select), CN_ENTRY_ADDRESS_TABLE, implode(' ', $where), $limit); $results = $wpdb->get_results($sql); return $results; }
/** * Renders a HTML tag attribute. * * @access public * @since 0.8 * * @uses sanitize_html_class() * @uses esc_attr() * * @param string $type The attribute name. * @param array|string $value The attribute value. * * @return string The rendered attribute. */ public static function attribute($type, $value) { switch ($type) { case 'class': if (is_array($value) && !empty($value)) { array_walk($value, 'sanitize_html_class'); return $value ? ' class="' . implode($value, ' ') . '" ' : ''; } elseif (!empty($value)) { return ' class="' . sanitize_html_class((string) $value) . '" '; } else { return ''; } break; case 'id': if (!empty($value)) { return ' id="' . esc_attr((string) $value) . '" '; } else { return ''; } break; case 'style': if (is_array($value) && !empty($value)) { array_walk($value, create_function('&$i, $property', '$i = "$property: $i";')); return $value ? ' style="' . implode($value, '; ') . '"' : ''; } return ''; break; case 'value': return ' value="' . esc_attr((string) $value) . '" '; break; case 'data': $data = array(); /** * Create valid HTML5 data attributes. * * @link http://stackoverflow.com/a/22753630/5351316 * @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset */ if (cnFunction::isDimensionalArray($value)) { foreach ($value as $_value) { if (isset($_value['name']) && 0 < strlen($_value['name'])) { $name = 'data-' . cnFormatting::toCamelCase($_value['name']); $data[$name] = $_value['value']; } } } else { if (isset($value['name']) && 0 < strlen($value['name'])) { $name = 'data-' . cnFormatting::toCamelCase($value['name']); $data[$name] = $value['value']; } } if (!empty($data)) { array_walk($data, create_function('&$i, $name', '$i = $name . \'="\' . esc_attr( $i ) . \'"\';')); return ' ' . implode($data, ' '); } return ''; break; default: if (is_array($value) && !empty($value)) { array_walk($value, 'esc_attr'); return $value ? ' ' . esc_attr($type) . '="' . implode($value, ' ') . '" ' : ''; } elseif (!empty($value)) { return ' ' . esc_attr($type) . '="' . esc_attr((string) $value) . '" '; } else { return ''; } } }
/** * Start the element output. * * @see Walker::start_el() * * @since 8.1.6 * * @uses esc_attr() * @uses number_format_i18n() * @uses cnTerm::get() * * @param string $output Passed by reference. Used to append additional content. * @param object $term Term object. * @param int $depth Depth of category in reference to parents. Default 0. * @param array $args An array of arguments. @see CN_Walker_Term_List::render() * @param int $id ID of the current term. */ public function start_el(&$output, $term, $depth = 0, $args = array(), $id = 0) { $indent = str_repeat("\t", $depth); $count = $args['show_count'] ? '<span class="cn-cat-count"> (' . esc_html(number_format_i18n($term->count)) . ')</span>' : ''; $url = cnTerm::permalink($term, 'category', $args); $html = sprintf('<a href="%1$s" title="%2$s">%3$s</a>', $url, esc_attr($term->name), esc_html($term->name) . $count); /** * Allows extensions to alter the HTML of term list item. * * @since 8.5.18 * * @param string $html The HTML. * @param cnTerm_Object $term The current term. * @param int $depth Depth of category. Used for tab indentation. * @param array $args The method attributes. */ $html = apply_filters('cn_term_list_item', $html, $term, $depth, $args); $class = array('cat-item', 'cat-item-' . $term->term_id, 'cn-cat-parent'); $termChildren = cnTerm::getTaxonomyTerms($term->taxonomy, array('parent' => $term->term_id, 'hide_empty' => FALSE, 'fields' => 'count')); if (!empty($termChildren)) { $class[] = 'cn-cat-has-children'; } if (!empty($args['current_category'])) { if (is_numeric($args['current_category'])) { $_current_category = cnTerm::get($args['current_category'], $term->taxonomy); // cnTerm::get() can return NULL || an instance of WP_Error, so, lets check for that. if (is_null($_current_category) || is_wp_error($_current_category)) { $_current_category = new stdClass(); $_current_category->parent = 0; } } else { $_current_category = new stdClass(); $_current_category->parent = 0; } if ($term->slug == $args['current_category']) { $class[] = ' current-cat'; } elseif ($term->term_id == $args['current_category']) { $class[] = ' current-cat'; } elseif ($term->term_id == $_current_category->parent) { $class[] = ' current-cat-parent'; } } /** * Allows extensions to add/remove class names to the current term list item. * * @since 8.5.18 * * @param array $class The array of class names. * @param cnTerm_Object $term The current term. * @param int $depth Depth of category. Used for tab indentation. * @param array $args The method attributes. */ $class = apply_filters('cn_term_list_item_class', $class, $term, $depth, $args); $class = cnSanitize::htmlClass($class); $output .= "{$indent}<li" . ' class="' . cnFunction::escAttributeDeep($class) . '"' . ">{$html}"; // Do not add EOL here, it'll add unwanted whitespace if terms are inline. }
/** * Returns as an array of objects containing the dates per the defined options for the current entry. * * Accepted options for the $atts property are: * preferred (bool) Retrieve the preferred entry date. * type (array) || (string) Retrieve specific date types. * Permitted Types: * baptism * certification * employment * membership * graduate_high_school * graduate_college * ordination * * Filters: * cn_date_atts => (array) Set the method attributes. * cn_date_cached => (bool) Define if the returned dates should be from the object cache or queried from the db. * cn_date => (object) Individual date as it is processed thru the loop. * cn_dates => (array) All dates before they are returned. * * @access public * @since 0.7.3 * @version 1.0 * @param array $atts Accepted values as noted above. * @param bool $cached Returns the cached date data rather than querying the db. * @param bool $saving Whether or no the data is being saved to the db. * * @return array */ public function getDates($atts = array(), $cached = TRUE, $saving = FALSE) { // Grab an instance of the Connections object. $instance = Connections_Directory(); $results = array(); $atts = apply_filters('cn_date_atts', $atts); $cached = apply_filters('cn_date_cached', $cached); /* * // START -- Set the default attributes array. \\ */ $defaults = array('preferred' => FALSE, 'type' => NULL); $atts = cnSanitize::args($atts, $defaults); $atts['id'] = $this->getId(); /* * // END -- Set the default attributes array if not supplied. \\ */ /* * Load back into the results the data from the legacy fields, anniversary and birthday, * for backward compatibility with versions 0.7.2.6 and older. */ if (!empty($this->anniversary)) { $anniversary = new stdClass(); $anniversary->id = 0; $anniversary->order = 0; $anniversary->preferred = FALSE; $anniversary->type = 'anniversary'; $anniversary->name = __('Anniversary', 'connections'); $anniversary->date = $this->getAnniversary('Y-m-d'); $anniversary->day = $this->getAnniversary('m-d'); $anniversary->visibility = 'public'; $results['anniversary'] = $anniversary; } if (!empty($this->birthday)) { $birthday = new stdClass(); $birthday->id = 0; $birthday->order = 0; $birthday->preferred = FALSE; $birthday->type = 'birthday'; $birthday->name = __('Birthday', 'connections'); $birthday->date = $this->getBirthday('Y-m-d'); $birthday->day = $this->getBirthday('m-d'); $birthday->visibility = 'public'; $results['birthday'] = $birthday; } if ($cached) { if (!empty($this->dates)) { $dates = unserialize($this->dates); if (empty($dates)) { return $results; } /** * @var bool $preferred * @var string $type */ extract($atts); /* * Covert to an array if it was supplied as a comma delimited string */ cnFunction::parseStringList($type); foreach ((array) $dates as $key => $date) { /** * Allow plugins to filter raw data before object is setup. * * @since 8.5.19 * * @param array $date */ $date = apply_filters('cn_date-pre_setup', $date); $row = new stdClass(); isset($date['id']) ? $row->id = (int) $date['id'] : ($row->id = 0); isset($date['order']) ? $row->order = (int) $date['order'] : ($row->order = 0); isset($date['preferred']) ? $row->preferred = (bool) $date['preferred'] : ($row->preferred = FALSE); isset($date['type']) ? $row->type = $this->format->sanitizeString($date['type']) : ($row->type = ''); isset($date['date']) ? $row->date = $this->format->sanitizeString($date['date']) : ($row->date = ''); isset($date['visibility']) ? $row->visibility = $this->format->sanitizeString($date['visibility']) : ($row->visibility = ''); /* * Set the date name based on the type. */ $dateTypes = $instance->options->getDateOptions(); $row->name = $dateTypes[$row->type]; /* * If the date type is anniversary or birthday and the date is equal to the date * saved in the legacy fields, unset the data imported from the legacy field. * This is for compatibility with versions 0.7.2.6 and older. * * NOTE: Only the month and day will be compared because the legacy getAnniversary() and getBirthday() methods * will return the year of the next anniversary or birthday. IE: if that date in the current year has already * passed the year would be the next year. */ if ('anniversary' == $row->type && isset($results['anniversary']) && substr($row->date, 5, 5) == $results['anniversary']->day) { unset($results['anniversary']); } if ('birthday' == $row->type && isset($results['birthday']) && substr($row->date, 5, 5) == $results['birthday']->day) { unset($results['birthday']); } /* * // START -- Do not return dates that do not match the supplied $atts. */ if ($preferred && !$row->preferred) { continue; } if (!empty($type) && !in_array($row->type, $type)) { continue; } /* * // END -- Do not return dates that do not match the supplied $atts. */ /* * // START -- Compatibility for previous versions. */ // Versions prior to 8.1.5 may not have visibility set, so we'll assume it was 'public' since it wasn't the option before. if (!isset($date['visibility']) || empty($date['visibility'])) { $row->visibility = 'public'; } /* * // END -- Compatibility for previous versions. */ // If the user does not have permission to view the address, do not return it. if (!$this->validate->userPermitted($row->visibility) && !$saving) { continue; } $results[] = apply_filters('cn_date', $row); } } } else { // Exit right away and return an emtpy array if the entry ID has not been set otherwise all dates will be returned by the query. if (!isset($this->id) || empty($this->id)) { return array(); } $dates = $instance->retrieve->dates($atts, $saving); if (empty($dates)) { return $results; } foreach ($dates as $date) { /** This filter is documented in ../includes/entry/class.entry-data.php */ $date = apply_filters('cn_date-pre_setup', $date); $date->id = (int) $date->id; $date->order = (int) $date->order; $date->preferred = (bool) $date->preferred; $date->type = $this->format->sanitizeString($date->type); $date->date = $this->format->sanitizeString($date->date); $date->visibility = $this->format->sanitizeString($date->visibility); /* * Set the date name based on the date type. */ $dateTypes = $instance->options->getDateOptions(); $date->name = $dateTypes[$date->type]; /* * If the date type is anniversary or birthday and the date is equal to the date * saved in the legacy fields are the same, unset the data imported from the legacy field. * This is for compatibility with versions 0.7.2.6 and older. */ if ('anniversary' == $date->type && isset($results['anniversary']) && $date->date == $results['anniversary']->date) { unset($results['anniversary']); } if ('birthday' == $date->type && isset($results['birthday']) && $date->date == $results['birthday']->date) { unset($results['birthday']); } /* * If the date type is anniversary or birthday and the date is equal to the date * saved in the legacy fields, unset the data imported from the legacy field. * This is for compatibility with versions 0.7.2.6 and older. * * NOTE: Only the month and day will be compared because the legacy getAnniversary() and getBirthday() methods * will return the year of the next anniversary or birthday. IE: if that date in the current year has already * passed the year would be the next year. */ if ('anniversary' == $date->type && isset($results['anniversary']) && substr($date->date, 5, 5) == $results['anniversary']->day) { unset($results['anniversary']); } if ('birthday' == $date->type && isset($results['birthday']) && substr($date->date, 5, 5) == $results['birthday']->day) { unset($results['birthday']); } $results[] = apply_filters('cn_date', $date); } } return apply_filters('cn_dates', $results); }
/** * Return an array of entry ID/s found with the supplied search terms. * * @todo Allow the fields for each table to be defined as a comma delimited list, convert an array and validate against of list of valid table fields. * @todo Add a filter to allow the search fields to be changed. * * Resources used: * http://devzone.zend.com/26/using-mysql-full-text-searching/ * http://onlamp.com/onlamp/2003/06/26/fulltext.html * * @since 0.7.2.0 * @param array $atts [optional] * * @return array */ public function search($atts = array()) { /** @var wpdb $wpdb */ global $wpdb; $results = array(); $scored = array(); $fields = cnSettingsAPI::get('connections', 'search', 'fields'); $fields = apply_filters('cn_search_fields', $fields); // If no search search fields are set, return an empty array. if (empty($fields)) { return array(); } /* * // START -- Set the default attributes array. \\ */ $defaults['terms'] = array(); if (in_array('family_name', $fields)) { $defaults['fields']['entry'][] = 'family_name'; } if (in_array('first_name', $fields)) { $defaults['fields']['entry'][] = 'first_name'; } if (in_array('middle_name', $fields)) { $defaults['fields']['entry'][] = 'middle_name'; } if (in_array('last_name', $fields)) { $defaults['fields']['entry'][] = 'last_name'; } if (in_array('title', $fields)) { $defaults['fields']['entry'][] = 'title'; } if (in_array('organization', $fields)) { $defaults['fields']['entry'][] = 'organization'; } if (in_array('department', $fields)) { $defaults['fields']['entry'][] = 'department'; } if (in_array('contact_first_name', $fields)) { $defaults['fields']['entry'][] = 'contact_first_name'; } if (in_array('contact_last_name', $fields)) { $defaults['fields']['entry'][] = 'contact_last_name'; } if (in_array('bio', $fields)) { $defaults['fields']['entry'][] = 'bio'; } if (in_array('notes', $fields)) { $defaults['fields']['entry'][] = 'notes'; } if (in_array('address_line_1', $fields)) { $defaults['fields']['address'][] = 'line_1'; } if (in_array('address_line_2', $fields)) { $defaults['fields']['address'][] = 'line_2'; } if (in_array('address_line_3', $fields)) { $defaults['fields']['address'][] = 'line_3'; } if (in_array('address_line_4', $fields)) { $defaults['fields']['address'][] = 'line_4'; } if (in_array('address_district', $fields)) { $defaults['fields']['address'][] = 'district'; } if (in_array('address_county', $fields)) { $defaults['fields']['address'][] = 'county'; } if (in_array('address_city', $fields)) { $defaults['fields']['address'][] = 'city'; } if (in_array('address_state', $fields)) { $defaults['fields']['address'][] = 'state'; } if (in_array('address_zipcode', $fields)) { $defaults['fields']['address'][] = 'zipcode'; } if (in_array('address_country', $fields)) { $defaults['fields']['address'][] = 'country'; } if (in_array('phone_number', $fields)) { $defaults['fields']['phone'][] = 'number'; } $atts = wp_parse_args($atts, apply_filters('cn_search_atts', $defaults)); // @todo Validate each fields array to ensure only permitted fields will be used. /* * // END -- Set the default attributes array if not supplied. \\ */ // If no search terms were entered, return an empty array. if (empty($atts['terms'])) { return array(); } // If value is a string, stripe the white space and covert to an array. //if ( ! is_array( $atts['terms'] ) ) $atts['terms'] = explode( ' ', trim( $atts['terms'] ) ); // Trim any white space from around the terms in the array. //array_walk( $atts['terms'] , 'trim' ); $original = $atts['terms']; $atts['terms'] = cnFunction::parseStringList($atts['terms'], '\\s'); array_unshift($atts['terms'], $original); $atts['terms'] = array_unique($atts['terms']); $atts['terms'] = apply_filters('cn_search_terms', $atts['terms']); // Remove any single characters and stop words from terms. $atts['terms'] = $this->parse_search_terms($atts['terms']); // If no search terms are left after removing stop words, return an empty array. if (empty($atts['terms'])) { return array(); } /* * Perform search using FULLTEXT if enabled. * * Perform the search on each table individually because joining the tables doesn't scale when * there are a large number of entries. * * NOTES: * The following is the error reported by MySQL when DB does not support FULLTEXT: 'The used table type doesn't support FULLTEXT indexes' * If DB does not support FULLTEXT the query will fail and the $results will be an empty array. * * FULLTEXT Restrictions as noted here: http://onlamp.com/onlamp/2003/06/26/fulltext.html * * Some of the default behaviors of these restrictions can be changed in your my.cnf or using the SET command * * FULLTEXT indices are NOT supported in InnoDB tables. * MySQL requires that you have at least three rows of data in your result set before it will return any results. * By default, if a search term appears in more than 50% of the rows then MySQL will not return any results. * By default, your search query must be at least four characters long and may not exceed 254 characters. * MySQL has a default stopwords file that has a list of common words (i.e., the, that, has) which are not returned in your search. In other words, searching for the will return zero rows. * According to MySQL's manual, the argument to AGAINST() must be a constant string. In other words, you cannot search for values returned within the query. */ if (cnSettingsAPI::get('connections', 'search', 'fulltext_enabled')) { $terms = array(); $shortwords = array(); /* * Remove any shortwords from the FULLTEXT query since the db will drop them anyway. * Add the shortwords to a separate array to be used to do a LIKE query. */ foreach ($atts['terms'] as $key => $term) { if (strlen($term) >= 2 && strlen($term) <= 3) { unset($atts['terms'][$key]); $shortwords[] = $term; } } if (!empty($atts['terms'])) { // Make each term required, functional AND query. $terms = apply_filters('cn_search_fulltext_terms', '+' . implode(' +', $atts['terms']), $atts['terms']); } /* * Only search the primary records if at least one fields is selected to be searched. */ if (!empty($atts['fields']['entry'])) { $select = array(); $from = array(); $where = array(); $like = array(); $select[] = 'SELECT ' . CN_ENTRY_TABLE . '.id'; /* * Set up the SELECT to return the results scored by relevance. */ if (!empty($terms)) { $select[] = $wpdb->prepare('MATCH (' . implode(', ', $atts['fields']['entry']) . ') AGAINST (%s) AS score', $terms); } $from[] = CN_ENTRY_TABLE; /* * If there are long word terms, perform a FULLTEXT query. */ if (!empty($terms)) { $where[] = $wpdb->prepare('MATCH (' . implode(', ', $atts['fields']['entry']) . ') AGAINST (%s IN BOOLEAN MODE)', $terms); } /* * If there are no long words and there are short words, perform a LIKE query for the short words. */ if (empty($terms) && !empty($shortwords)) { foreach ($shortwords as $word) { /** * Allow plugins to alter the shortword LIKE query. * * By default the shortword LIKE query will only return results with entries matching words that * begin with the shortword. This is done to match the FULLTEXT search query which only returns * results with terms that being with the search term. * * To alter the LIKE query to return results for shortword where entries contain the shortword, * rather than begins with, use this example filter: * * <code> * add_filter( 'cn_search_like_shortword', 'my_custom_search_like_shortword', 10, 2 ); * function my_custom_search_like_shortword( $esc_word, $word ) { * * return '%' . $wpdb->esc_like( $word ) . '%'; * } * </code> * * @since 8.5.27 * * @param string $word The shortword where the $word is escaped for a LIKE query with a * trailing `%` so the LIKE query will return results that begin with $word. * @param string $word The shortword to perform the LIKE query with. */ $word = apply_filters('cn_search_like_shortword', $wpdb->esc_like($word) . '%', $word); $like[] = $wpdb->prepare(implode(' LIKE %s OR ', $atts['fields']['entry']) . ' LIKE %s ', array_fill(0, count($atts['fields']['entry']), $word)); } $where[] = '( ' . implode(') OR (', $like) . ')'; } /* * Return the query results ordered by the relevance score. */ $orderBy = empty($terms) ? '' : ' ORDER BY score'; $sql = implode(', ', $select) . ' FROM ' . implode(',', $from) . ' WHERE ' . implode(' AND ', $where) . $orderBy; $scored = $wpdb->get_results($sql, ARRAY_A); } /* * Only search the address records if at least one fields is selected to be searched. */ if (!empty($atts['fields']['address'])) { $select = array(); $from = array(); $where = array(); $like = array(); $select[] = 'SELECT ' . CN_ENTRY_ADDRESS_TABLE . '.entry_id'; /* * Set up the SELECT to return the results scored by relevance. */ if (!empty($terms)) { $select[] = $wpdb->prepare('MATCH (' . implode(', ', $atts['fields']['address']) . ') AGAINST (%s) AS score', $terms); } $from[] = CN_ENTRY_ADDRESS_TABLE; /* * If there are long word terms, perform a FULLTEXT query. */ if (!empty($terms)) { $where[] = $wpdb->prepare('MATCH (' . implode(', ', $atts['fields']['address']) . ') AGAINST (%s IN BOOLEAN MODE)', $terms); } /* * If there are no long words and there are short words, perform a LIKE query for the short words. */ if (empty($terms) && !empty($shortwords)) { foreach ($shortwords as $word) { $word = apply_filters('cn_search_like_shortword', $wpdb->esc_like($word) . '%', $word); $like[] = $wpdb->prepare(implode(' LIKE %s OR ', $atts['fields']['address']) . ' LIKE %s ', array_fill(0, count($atts['fields']['address']), $word)); } $where[] = '( ' . implode(') OR (', $like) . ')'; } /* * Return the query results ordered by the relevance score. */ $orderBy = empty($terms) ? '' : ' ORDER BY score'; $sql = implode(', ', $select) . ' FROM ' . implode(',', $from) . ' WHERE ' . implode(' AND ', $where) . $orderBy; $ids = $wpdb->get_results($sql, ARRAY_A); /* * If any results are returned merge them in to the $scored results. */ if (!empty($ids)) { $scored = array_merge($scored, $ids); } } /* * Only search the phone records if the field is selected to be search. */ if (!empty($atts['fields']['phone'])) { $select = array(); $from = array(); $where = array(); $like = array(); $select[] = 'SELECT ' . CN_ENTRY_PHONE_TABLE . '.entry_id'; /* * Set up the SELECT to return the results scored by relevance. */ if (!empty($terms)) { $select[] = $wpdb->prepare('MATCH (' . implode(', ', $atts['fields']['phone']) . ') AGAINST (%s) AS score', $terms); } $from[] = CN_ENTRY_PHONE_TABLE; /* * If there are long word terms, perform a FULLTEXT query. */ if (!empty($terms)) { $where[] = $wpdb->prepare('MATCH (' . implode(', ', $atts['fields']['phone']) . ') AGAINST (%s IN BOOLEAN MODE)', $terms); } /* * If there are no long words and there are short words, perform a LIKE query for the short words. */ if (empty($terms) && !empty($shortwords)) { foreach ($shortwords as $word) { $word = apply_filters('cn_search_like_shortword', $wpdb->esc_like($word) . '%', $word); $like[] = $wpdb->prepare(implode(' LIKE %s OR ', $atts['fields']['phone']) . ' LIKE %s ', array_fill(0, count($atts['fields']['phone']), $word)); } $where[] = '( ' . implode(') OR (', $like) . ')'; } /* * Return the query results ordered by the relevance score. */ $orderBy = empty($terms) ? '' : ' ORDER BY score'; $sql = implode(', ', $select) . ' FROM ' . implode(',', $from) . ' WHERE ' . implode(' AND ', $where) . $orderBy; $ids = $wpdb->get_results($sql, ARRAY_A); /* * If any results are returned merge them in to the $scored results. */ if (!empty($ids)) { $scored = array_merge($scored, $ids); } } $scored = apply_filters('cn_search_scored_results', $scored, $terms); /* * The query results are stored in the $scored array ordered by relevance. * Only the entry ID/s are needed to be returned. Setup the $results array * with only the entry ID/s in the same order as returned by the relevance score. */ foreach ($scored as $entry) { $results[] = isset($entry['id']) ? $entry['id'] : $entry['entry_id']; } } /* * If no results are found, perhaps to the way MySQL performs FULLTEXT queries, FULLTEXT search being disabled * or the DB not supporting FULLTEXT, run a LIKE query. * * Perform the search on each table individually because joining the tables doesn't scale when * there are a large number of entries. */ if ((cnSettingsAPI::get('connections', 'search', 'keyword_enabled') && empty($results) || cnSettingsAPI::get('connections', 'search', 'fulltext_enabled') && empty($results)) && !empty($atts['terms'])) { /* * Only search the primary records if at least one fields is selected to be searched. */ if (!empty($atts['fields']['entry'])) { $like = array(); // Reset the like array. foreach ($atts['terms'] as $term) { /* * Attempt to secure the query using $wpdb->prepare() and like_escape() * * Since $wpdb->prepare() required var for each directive in the query string we'll use array_fill * where the count based on the number of columns that will be searched. */ $like[] = $wpdb->prepare(implode(' LIKE %s OR ', $atts['fields']['entry']) . ' LIKE %s ', array_fill(0, count($atts['fields']['entry']), '%' . $wpdb->esc_like($term) . '%')); } $sql = 'SELECT ' . CN_ENTRY_TABLE . '.id FROM ' . CN_ENTRY_TABLE . ' WHERE (' . implode(') OR (', $like) . ')'; //print_r($sql); $results = array_merge($results, $wpdb->get_col($sql)); //print_r($results);die; } /* * Only search the address records if at least one fields is selected to be searched. */ if (!empty($atts['fields']['address'])) { $like = array(); // Reset the like array. foreach ($atts['terms'] as $term) { /* * Attempt to secure the query using $wpdb->prepare() and like_escape() * * Since $wpdb->prepare() required var for each directive in the query string we'll use array_fill * where the count based on the number of columns that will be searched. */ $like[] = $wpdb->prepare(implode(' LIKE %s OR ', $atts['fields']['address']) . ' LIKE %s ', array_fill(0, count($atts['fields']['address']), '%' . $wpdb->esc_like($term) . '%')); } $sql = 'SELECT ' . CN_ENTRY_ADDRESS_TABLE . '.entry_id FROM ' . CN_ENTRY_ADDRESS_TABLE . ' WHERE (' . implode(') OR (', $like) . ')'; //print_r($sql); $results = array_merge($results, $wpdb->get_col($sql)); //print_r($results); } /* * Only search the phone records if the field is selected to be search. */ if (!empty($atts['fields']['phone'])) { $like = array(); // Reset the like array. foreach ($atts['terms'] as $term) { /* * Attempt to secure the query using $wpdb->prepare() and like_escape() * * Since $wpdb->prepare() required var for each directive in the query string we'll use array_fill * where the count based on the number of columns that will be searched. */ $like[] = $wpdb->prepare(implode(' LIKE %s OR ', $atts['fields']['phone']) . ' LIKE %s ', array_fill(0, count($atts['fields']['phone']), '%' . $wpdb->esc_like($term) . '%')); } $sql = 'SELECT ' . CN_ENTRY_PHONE_TABLE . '.entry_id FROM ' . CN_ENTRY_PHONE_TABLE . ' WHERE (' . implode(') OR (', $like) . ')'; //print_r($sql); $results = array_merge($results, $wpdb->get_col($sql)); //print_r($results); } } return apply_filters('cn_search_results', $results, $atts); }
/** * Import settings from a JSON encoded string. * * @access private * @since 8.3 * @static * * @param string $json * * @return bool|string */ public static function import($json) { $result = cnFunction::decodeJSON($json, TRUE); if (is_wp_error($result)) { return $result->get_error_message('json_decode_error'); } foreach ($result as $pluginID => $options) { foreach ($options as $id => $value) { update_option($id, $value); } } return TRUE; }
/** * Run the actions registered to custom content blocks. * * Render any custom content blocks registered to the `cn_entry_output_content-{id}` action hook. * * This will also run any actions registered for a custom metaboxes and its fields. * The actions should hook into `cn_output_meta_field-{key}` to be rendered. * * Accepted option for the $atts property are: * id (string) The custom block ID to render. * order (mixed) array | string An indexed array of custom content block IDs that should be rendered in the order in the array. * If a string is provided, it should be a comma delimited string containing the content block IDs. It will be converted to an array. * exclude (array) An indexed array of custom content block IDs that should be excluded from being rendered. * include (array) An indexed array of custom content block IDs that should be rendered. * NOTE: Custom content block IDs in `exclude` outweigh custom content block IDs in include. Meaning if the * same custom content block ID exists in both, the custom content block will be excluded. * * @access public * @since 0.8 * @uses do_action() * @uses wp_parse_args() * @uses apply_filters() * @uses has_action() * @param mixed $atts array | string [optional] The custom content block(s) to render. * @param array $shortcode_atts [optional] If this is used within the shortcode template loop, the shortcode atts * should be passed so the shortcode atts can be passed by do_action() to allow access to the action callback. * @param cnTemplate|null $template [optional] If this is used within the shortcode template loop, the template object * should be passed so the template object can be passed by do_action() to allow access to the action callback. * * @return string The HTML output of the custom content blocks. */ public function getContentBlock($atts = array(), $shortcode_atts = array(), $template = NULL) { $blockContainerContent = ''; if (cnQuery::getVar('cn-entry-slug')) { $settings = cnSettingsAPI::get('connections', 'connections_display_single', 'content_block'); } else { $settings = cnSettingsAPI::get('connections', 'connections_display_list', 'content_block'); } $order = isset($settings['order']) ? $settings['order'] : array(); $include = isset($settings['active']) ? $settings['active'] : array(); $exclude = empty($include) ? $order : array(); $titles = array(); $defaults = array('id' => '', 'order' => is_string($atts) && !empty($atts) ? $atts : $order, 'exclude' => is_string($atts) && !empty($atts) ? '' : $exclude, 'include' => is_string($atts) && !empty($atts) ? '' : $include, 'layout' => 'list', 'container_tag' => 'div', 'block_tag' => 'div', 'header_tag' => 'h3', 'before' => '', 'after' => '', 'return' => FALSE); $atts = wp_parse_args(apply_filters('cn_output_content_block_atts', $atts), $defaults); if (!empty($atts['id'])) { $blocks = array($atts['id']); } elseif (!empty($atts['order'])) { $blocks = cnFunction::parseStringList($atts['order'], ','); } // Nothing to render, exit. if (empty($blocks)) { return ''; } // Cleanup user input. Trim whitespace and convert to lowercase. $blocks = array_map('strtolower', array_map('trim', $blocks)); // Output the registered action in the order supplied by the user. foreach ($blocks as $key) { isset($blockNumber) ? $blockNumber++ : ($blockNumber = 1); // Exclude/Include the metaboxes that have been requested to exclude/include. if (!empty($atts['exclude'])) { if (in_array($key, $atts['exclude'])) { continue; } } else { if (!empty($atts['include'])) { if (!in_array($key, $atts['include'])) { continue; } } } ob_start(); // If the hook has a registered meta data output callback registered, lets run it. if (has_action('cn_output_meta_field-' . $key)) { // Grab the meta. $results = $this->getMeta(array('key' => $key, 'single' => TRUE)); if (!empty($results)) { do_action("cn_output_meta_field-{$key}", $key, $results, $this, $shortcode_atts, $template); } } // Render the "Custom Fields" meta block content. if ('meta' == $key) { $this->getMetaBlock(array(), $shortcode_atts, $template); } $hook = "cn_entry_output_content-{$key}"; if (has_action($hook)) { do_action($hook, $this, $shortcode_atts, $template); } $blockContent = ob_get_clean(); if (empty($blockContent)) { continue; } $blockID = $this->getSlug() . '-' . $blockNumber; // Store the title in an array that can be accessed/passed from outside the content block loop. // And if there is no title for some reason, create one from the key. if ($name = cnOptions::getContentBlocks($key)) { $titles[$blockID] = $name; } elseif ($name = cnOptions::getContentBlocks($key, 'single')) { $titles[$blockID] = $name; } else { $titles[$blockID] = ucwords(str_replace(array('-', '_'), ' ', $key)); } //$titles[ $blockID ] = cnOptions::getContentBlocks( $key ) ? cnOptions::getContentBlocks( $key ) : ucwords( str_replace( array( '-', '_' ), ' ', $key ) ); $blockContainerContent .= apply_filters('cn_entry_output_content_block', sprintf('<%2$s class="cn-entry-content-block cn-entry-content-block-%3$s" id="cn-entry-content-block-%4$s">%1$s%5$s</%2$s>' . PHP_EOL, sprintf('<%1$s>%2$s</%1$s>', $atts['header_tag'], $titles[$blockID]), $atts['block_tag'], $key, $blockID, $blockContent), $this, $key, $blockID, $titles[$blockID], $blockContent, $atts, $shortcode_atts); } if (empty($blockContainerContent)) { return ''; } $out = apply_filters('cn_entry_output_content_block_container', sprintf('<%1$s class="cn-entry-content-block-%2$s">%3$s</%1$s>' . PHP_EOL, $atts['container_tag'], $atts['layout'], $blockContainerContent), $this, $blockContainerContent, $titles, $atts, $shortcode_atts); $out = $atts['before'] . $out . $atts['after'] . PHP_EOL; return $this->echoOrReturn($atts['return'], $out); }