/** * Copies folders * * @return void */ public function copyAssets() { foreach (Helper::ensureArray($this->config['copy']) as $folder) { $folder = str_replace('{theme}', Config::getTheme(), $folder); $full_from_path = Path::assemble(BASE_PATH, $folder); $full_to_path = Path::assemble(BASE_PATH, $this->config['destination'], $folder); Folder::copy($full_from_path, $full_to_path); } }
public function render() { // Let's make sure they set an upload destination if (array_get($this->field_config, 'destination', false) === false) { throw new Exception("You need to set a destination for your File field."); } // Normalize the destination $this->destination = trim(array_get($this->field_config, 'destination'), '/') . '/'; // Allow a string or an array, but we want an array $has_data = ($this->field_data != ''); $this->field_data = Helper::ensureArray($this->field_data); // Clean up {{ _site_root }} and lack of leading slash existence foreach ($this->field_data as $i => $file) { $this->field_data[$i] = URL::tidy('/' . str_replace('{{ _site_root }}', '', $file)); } // Whether or not to allow the browse existing files functionality $allow_browse = array_get($this->field_config, 'browse', true); // Resizing config if ($resize = array_get($this->field_config, 'resize')) { $resize['resize'] = true; $resize = http_build_query($resize); } // If we're in a subdirectory, prepend it to all the filenames if (($site_root = Config::getSiteRoot()) != '/') { foreach ($this->field_data as $i => $file) { $this->field_data[$i] = URL::assemble($site_root, $file); } } // Send data to the view $vars = array( 'field_id' => $this->field_id, 'field_name' => $this->fieldname, 'tabindex' => $this->tabindex, 'has_data' => $has_data, 'field_data' => $this->field_data, 'field_config' => $this->field_config, 'destination' => $this->destination, 'allow_browse' => $allow_browse, 'server_files' => ($allow_browse) ? json_encode($this->tasks->generateModal($this->field_config, $this->destination)) : null, 'file_thumb' => URL::assemble(Config::getSiteRoot(), Config::get('admin_path'), 'themes', Config::get('admin_theme'), '/img/file.png'), 'resize' => $resize ); $template = File::get($this->getAddonLocation() . 'views/fieldtype.html'); return Parse::template($template, $vars); }
public function render() { $options = array_get($this->field_config, 'options', array()); $html = '<input type="hidden" name="' . $this->fieldname . '" value="false" />'; foreach ($options as $key => $option) { $attributes = array('name' => $this->fieldname . '[]', 'id' => $this->field_id . '_' . $key, 'class' => 'checkbox', 'tabindex' => $this->tabindex, 'value' => $key, 'checked' => ''); if ($this->field_data && in_array($key, Helper::ensureArray($this->field_data))) { $attributes['checked'] = 'checked'; } $html .= '<div class="checkbox-block">'; $html .= HTML::makeInput('checkbox', $attributes, $this->is_required); $html .= '<label for="' . $this->field_id . '_' . $key . '">' . $option . '</label>'; $html .= '</div>'; } return $html; }
public function render() { // Generate a hash unique to this field's config and data $hash = Helper::makeHash($this->field_config, $this->field_data); // If we've already saved the output, grab it from blink's cache // and avoid further processing. if ($this->blink->exists($hash)) { $html = $this->blink->get($hash); return $this->renderFieldReplacements($html); } // Let's make sure they set an upload destination if (array_get($this->field_config, 'destination', false) === false) { throw new Exception("You need to set a destination for your File field."); } // Normalize the destination $this->destination = trim(array_get($this->field_config, 'destination'), '/') . '/'; // Allow a string or an array, but we want an array $has_data = $this->field_data != ''; $this->field_data = Helper::ensureArray($this->field_data); // Clean up {{ _site_root }} and lack of leading slash existence foreach ($this->field_data as $i => $file) { $this->field_data[$i] = URL::tidy('/' . str_replace('{{ _site_root }}', '', $file)); } // Whether or not to allow the browse existing files functionality $allow_browse = array_get($this->field_config, 'browse', true); // Resizing config if ($resize = array_get($this->field_config, 'resize')) { $resize['resize'] = true; $resize = http_build_query($resize); } // If we're in a subdirectory, prepend it to all the filenames if (($site_root = Config::getSiteRoot()) != '/') { foreach ($this->field_data as $i => $file) { $this->field_data[$i] = URL::assemble($site_root, $file); } } // Send data to the view $vars = array('field_id' => $this->field_id, 'field_name' => $this->fieldname, 'tabindex' => $this->tabindex, 'has_data' => $has_data, 'field_data' => $this->field_data, 'field_config' => $this->field_config, 'destination' => $this->destination, 'allow_browse' => $allow_browse, 'browse_url' => URL::assemble(Config::getSiteRoot(), Config::get('admin_path') . '.php/files?config=' . rawurlencode(Helper::encrypt(serialize($this->field_config)))), 'file_thumb' => $this->tasks->defaultFileThumbnail(), 'resize' => $resize); // Get the view template from the file $template = File::get($this->getAddonLocation() . 'views/fieldtype.html'); // Parse it $html = Parse::template($template, $vars); // Save it to cache for other similar fields $this->blink->set($hash, $html); // Output! return $this->renderFieldReplacements($html); }
/** * Get an item from an array using "colon" notation. * * <code> * // Get the $array['user']['name'] value from the array * $name = array_get($array, 'user:name'); * * // Return a default from if the specified item doesn't exist * $name = array_get($array, 'user:name', 'Batman'); * * // Return the first found key, otherwise a default * $name = array_get($array, array('user:name', 'user:first_name'), 'Bruce'); * </code> * * @param array $array * @param string|array $key * @param mixed $default * @return mixed */ function array_get($array, $key, $default = null) { if (is_null($key)) return $array; $keys = Helper::ensureArray($key); // short-circuit if (!is_array($array)) { return Helper::resolveValue($default); } // a flag to remember whether something has been found or not $found = false; // To retrieve the array item using dot syntax, we'll iterate through // each segment in the key and look for that value. If it exists, we // will return it, otherwise we will set the depth of the array and // look for the next segment. foreach ($keys as $key) { foreach (explode(':', $key) as $segment) { if (!is_array($array) || !array_key_exists($segment, $array)) { // did we not find something? mark `found` as `false` $found = false; break; } // we found something, although not sure if this is the last thing, // mark `found` as `true` and let the outer loop handle it if this // *is* the last thing in the list $found = true; $array = $array[$segment]; } // if `found` is `true`, the inner loop found something worth returning, // which means that we're done here if ($found) { break; } } if ($found) { // `found` is `true`, we found something, return that return $array; } else { // `found` isn't `true`, return the default return Helper::resolveValue($default); } }
/** * Fetches a value from the user-passed parameters * * @param mixed $keys Key of value to retrieve, an array will allow fallback param names * @param mixed $default Default value if no value is found * @param callable $validity_check Allows a callback function to validate parameter * @param boolean $is_boolean Indicates parameter is boolean * @param boolean $force_lower Force the parameter's value to be lowercase? * @return mixed */ protected function fetchParam($keys, $default = NULL, $validity_check = NULL, $is_boolean = FALSE, $force_lower = TRUE) { $keys = Helper::ensureArray($keys); foreach ($keys as $key) { if (isset($this->attributes[$key])) { $value = $force_lower ? strtolower($this->attributes[$key]) : $this->attributes[$key]; if (!$validity_check || $validity_check && is_callable($validity_check) && $validity_check($value) === TRUE) { // account for yes/no parameters if ($is_boolean === TRUE) { return !in_array(strtolower($value), array("no", "false", "0", "", "-1")); } // otherwise, standard return return $value; } } } return $default; }
/** * Delete one or more keys' hashes * * @param string|array $keys Key or keys whose hashes should be deleted * @return bool */ public function deleteByKey($keys) { $keys = Helper::ensureArray($keys); foreach ($keys as $key) { if (!$this->cache->exists('keys/' . $key)) { continue; } // grab the hashes to invalidate $hashes = $this->cache->getYAML('keys/' . $key); foreach ($hashes as $hash) { // delete it if ($this->cache->exists('troves/' . $hash)) { $this->cache->delete('troves/' . $hash); } } } return true; }
/** * Filter * * @param array $filters Filter list * @return void */ public function filter($filters) { $folders = array(); $min_count = 0; if (isset($filters['folders'])) { $folders = Helper::ensureArray($filters['folders']); } if (isset($filters['min_count'])) { $min_count = (int) $filters['min_count']; } $data = $this->data; foreach ($data as $value => $parts) { $filters = array('folders' => $folders); $parts['content']->filter($filters); $parts['count'] = $parts['content']->count(); if ($parts['count'] < $min_count) { unset($data[$value]); } } $this->data = $data; }
/** * Filter * * @param array $filters Filter list * @return void */ public function filter($filters) { $min_count = 0; $given_filters = $filters; $filters = array('min_count' => isset($given_filters['min_count']) ? $given_filters['min_count'] : null, 'show_drafts' => isset($given_filters['show_drafts']) ? $given_filters['show_drafts'] : null, 'show_hidden' => isset($given_filters['show_hidden']) ? $given_filters['show_hidden'] : null, 'since' => isset($given_filters['since']) ? $given_filters['since'] : null, 'until' => isset($given_filters['until']) ? $given_filters['until'] : null, 'show_past' => isset($given_filters['show_past']) ? $given_filters['show_past'] : null, 'show_future' => isset($given_filters['show_future']) ? $given_filters['show_future'] : null, 'type' => isset($given_filters['type']) ? strtolower($given_filters['type']) : null, 'conditions' => isset($given_filters['conditions']) ? $given_filters['conditions'] : null, 'where' => isset($given_filters['where']) ? $given_filters['where'] : null, 'folders' => isset($given_filters['folders']) ? $given_filters['folders'] : null, 'located' => isset($given_filters['located']) ? $given_filters['located'] : null); // fix folders to be an array if (!is_null($filters['folders'])) { $filters['folders'] = Helper::ensureArray($filters['folders']); } if (!is_null($filters['min_count'])) { $min_count = (int) $filters['min_count']; } $data = $this->data; foreach ($data as $value => $parts) { $parts['content']->filter($filters); $parts['count'] = $parts['content']->count(); if ($parts['count'] < $min_count) { unset($data[$value]); } } $this->data = $data; // re-tally results $this->tallyResults(); }
public function process($settings) { // If empty, save as null if ($this->field_data === '') { return null; } // If we're forcing lowercase taxonomies (which we are by default), save them as lower too if (array_get($settings, 'taxonomy', false) && Config::get('taxonomy_force_lowercase', false)) { $this->field_data = Helper::ensureArray($this->field_data); foreach ($this->field_data as $key => $value) { $this->field_data[$key] = strtolower($value); } } return $this->field_data; }
private function metricTally($config, $data) { $field = $config['field']; $metrics = array(); foreach ($data as $item) { $values = Helper::ensureArray($item['fields'][$field]); foreach ($values as $value) { if (array_get($metrics, $value)) { $metrics[$value] = $metrics[$value] + 1; } else { $metrics[$value] = 1; } } } if ($sort_by = array_get($config, 'sort_by')) { if ($sort_by === 'key') { ksort($metrics); } elseif ($sort_by === 'value') { arsort($metrics); } if (array_get($config, 'sort_dir') === 'desc') { $metrics = array_reverse($metrics, true); } } $config['metrics'] = $metrics; return $config; }
/** * Supplements the content in the set * * @param array $context Context for supplementing * @return void */ public function supplement($context = array()) { $hash = Debug::markStart('content', 'supplementing'); if ($this->supplemented) { return; } $this->supplemented = true; $context = Helper::ensureArray($context); // determine context $given_context = $context; $context = array('locate_with' => isset($given_context['locate_with']) ? $given_context['locate_with'] : null, 'center_point' => isset($given_context['center_point']) ? $given_context['center_point'] : null, 'list_helpers' => isset($given_content['list_helpers']) ? $given_context['list_helpers'] : true, 'context_urls' => isset($given_context['context_urls']) ? $given_context['context_urls'] : true, 'total_found' => isset($given_context['total_found']) ? $given_context['total_found'] : null, 'group_by_date' => isset($given_context['group_by_date']) ? $given_context['group_by_date'] : null, 'inherit_folder_data' => isset($given_context['inherit_folder_data']) ? $given_context['inherit_folder_data'] : true, 'merge_with_data' => isset($given_context['merge_with_data']) ? $given_context['merge_with_data'] : true); // set up helper variables $center_point = false; if ($context['center_point'] && preg_match(Pattern::COORDINATES, $context['center_point'], $matches)) { $center_point = array($matches[1], $matches[2]); } // contextual urls are based on current page, not individual data records // we can figure this out once and then set it with each one if ($context['context_urls']) { $raw_url = Request::getResourceURI(); $page_url = Path::tidy($raw_url); } // iteration memory $last_date = null; // loop through content, supplementing each record with data foreach ($this->content as $content_key => $data) { // locate if ($context['locate_with']) { $location_data = isset($data[$context['locate_with']]) ? $data[$context['locate_with']] : null; // check that location data is fully set if (is_array($location_data) && isset($location_data['latitude']) && $location_data['latitude'] && isset($location_data['longitude']) && $location_data['longitude']) { $data['latitude'] = $location_data['latitude']; $data['longitude'] = $location_data['longitude']; $data['coordinates'] = $location_data['latitude'] . "," . $location_data['longitude']; // get distance from center if ($center_point) { $location = array($data['latitude'], $data['longitude']); $data['distance_km'] = Math::getDistanceInKilometers($center_point, $location); $data['distance_mi'] = Math::convertKilometersToMiles($data['distance_km']); } } } // contextual urls if ($context['context_urls']) { $data['raw_url'] = $raw_url; $data['page_url'] = $page_url; } // total entries if ($context['total_found']) { $data['total_found'] = (int) $context['total_found']; } // group by date if ($context['group_by_date'] && $data['datestamp']) { $formatted_date = Date::format($context['group_by_date'], $data['datestamp']); if ($formatted_date !== $last_date) { $last_date = $formatted_date; $data['grouped_date'] = $formatted_date; } else { $data['grouped_date'] = ''; } } // loop through content to add data for variables that are arrays foreach ($data as $key => $value) { // Only run on zero indexed arrays/loops if (is_array($value) && isset($value[0]) && !is_array($value[0])) { // list helpers if ($context['list_helpers']) { // make automagic lists $data[$key . "_list"] = join(", ", $value); $data[$key . "_spaced_list"] = join(" ", $value); $data[$key . "_option_list"] = join("|", $value); $data[$key . "_ordered_list"] = "<ol><li>" . join("</li><li>", $value) . "</li></ol>"; $data[$key . "_unordered_list"] = "<ul><li>" . join("</li><li>", $value) . "</li></ul>"; $data[$key . "_sentence_list"] = Helper::makeSentenceList($value); $data[$key . "_ampersand_sentence_list"] = Helper::makeSentenceList($value, "&", false); // handle taxonomies if (Taxonomy::isTaxonomy($key)) { $url_list = array_map(function ($item) use($data, $key, $value) { return '<a href="' . Taxonomy::getURL($data['_folder'], $key, $item) . '">' . $item . '</a>'; }, $value); $data[$key . "_url_list"] = join(", ", $url_list); $data[$key . "_spaced_url_list"] = join(" ", $url_list); $data[$key . "_ordered_url_list"] = "<ol><li>" . join("</li><li>", $url_list) . "</li></ol>"; $data[$key . "_unordered_url_list"] = "<ul><li>" . join("</li><li>", $url_list) . "</li></ul>"; $data[$key . "_sentence_url_list"] = Helper::makeSentenceList($url_list); $data[$key . "_ampersand_sentence_url_list"] = Helper::makeSentenceList($url_list, "&", false); } } } } // update content with supplemented data merged with global config data if ($context['merge_with_data'] || $context['inherit_folder_data']) { $folder_data = array(); $all_config = array(); if ($context['inherit_folder_data']) { $folder_data = $this->getFolderData($data['_file']); } if ($context['merge_with_data']) { $all_config = Config::getAll(); } // merge them all together $this->content[$content_key] = $data + $folder_data + $all_config; } else { $this->content[$content_key] = $data; } } Debug::markEnd($hash); }
/** * Gets a tree of content information * * @param string $base_url URL for the base of the tree to load * @param int $depth Number of levels deep to return * @param boolean $folders_only Folders only * @param boolean $include_entries Should we include entries in our tree? * @param boolean $show_hidden Should we not include hidden content * @param boolean $include_content Should we include content from the found info? * @param mixed $exclude Array of URLs to exclude * @return array */ public static function getContentTree($base_url, $depth = 12, $folders_only = true, $include_entries = false, $show_hidden = false, $include_content = false, $exclude = false) { // load structure and set up variables self::loadStructure(); $output = array(); // exclude URLs $exclude = Helper::ensureArray($exclude); // no depth asked for if ($depth == 0) { return array(); } // make sure we can find the requested URL in the structure if (!isset(self::$structure[$base_url])) { Log::debug('Could not find URL in structure cache.', 'core', 'ContentService'); return array(); } // depth measurements $starting_depth = self::$structure[$base_url]['depth'] + 1; // start one deeper than the base URL's depth $current_depth = $starting_depth; // recursively grab the tree foreach (self::$structure as $url => $data) { // is this the right depth and not the 404 page? if ($data['depth'] !== $current_depth || $url == "/404") { continue; } // is this under the appropriate parent? if (!Pattern::startsWith(Path::tidy($data['parent'] . '/'), Path::tidy($base_url . '/'))) { continue; } // is this hidden? if ($data['is_draft'] || !$show_hidden && $data['is_hidden']) { continue; } // is this an entry when we don't want them? if (!$include_entries && $data['is_entry'] && !$data['is_page']) { continue; } // is this a non-folder when all we want is folders? if ($folders_only && $data['type'] != 'folder') { continue; } // is this in the excluded URLs list? if (in_array($url, $exclude)) { continue; } // get parent url $parent_url = substr($url, 0, strrpos($url, '/')); $parent_url = $parent_url == "" ? Config::getSiteRoot() : $parent_url; // look up parent data in cache if (!isset(self::$parent_cache[$parent_url])) { // doesn't exist, load it up $parent_data = Content::get($parent_url, $include_content, false); if ($include_content) { // give them everything $parent = $parent_data; } else { // just the bare necessities $parent = array('title' => isset($parent_data['title']) ? $parent_data['title'] : '', 'url' => isset($parent_data['url']) ? $parent_data['url'] : ''); } // now stick this in the cache for next time self::$parent_cache[$parent_url] = $parent; } // get information $content = Content::get($url, $include_content, false); // data to be returned to the tree $for_output = array('type' => $data['type'], 'title' => isset($content['title']) ? $content['title'] : '', 'slug' => $content['slug'], 'url' => $url, 'depth' => $current_depth, 'children' => self::getContentTree($url, $depth - 1, $folders_only, $include_entries, $show_hidden, $include_content, $exclude), 'is_current' => URL::getCurrent() == $url, 'is_parent' => URL::getCurrent() != $url && Pattern::startsWith(URL::getCurrent(), $url . '/'), 'is_entry' => $data['is_entry'], 'is_page' => $data['is_page'], 'is_folder' => $data['type'] == 'folder', 'order_key' => $data['order_key'], 'sub_order_key' => $data['sub_order_key'], 'parent' => array(self::$parent_cache[$parent_url])); // if we're including content, merge that in if ($include_content) { $for_output = $content + $for_output; } // add it to the list $output[] = $for_output; } // now we need to sort the nav items uasort($output, function ($a, $b) { // sort on order_key $result = Helper::compareValues($a['order_key'], $b['order_key']); // if those matched, sort on sub_order_key if ($result === 0) { $result = Helper::compareValues($a['sub_order_key'], $b['sub_order_key']); } // return 1 or 0 or -1, whatever we ended up with return $result; }); // re-key the array $output = array_values($output); // return what we know return $output; }
/** * Checks to see if this member has a given $role * * @param mixed $role Role to check * @return boolean */ public function hasRole($role) { $role = array_map('strtolower', Helper::ensureArray($role)); $member_roles = array_map('strtolower', $this->get('roles')); // check for intersection $matches = array_intersect($member_roles, $role); return (bool) count($matches); }
/** * Updates the internal content cache * * @return boolean */ public static function update() { // start measuring $content_hash = Debug::markStart('caching', 'content'); // track if any files have changed $files_changed = false; $settings_changed = false; $members_changed = false; // grab length of content type extension $content_type = Config::getContentType(); $full_content_root = rtrim(Path::tidy(BASE_PATH . "/" . Config::getContentRoot()), "/"); $content_type_length = strlen($content_type) + 1; // the cache files we'll use $cache_file = BASE_PATH . '/_cache/_app/content/content.php'; $settings_file = BASE_PATH . '/_cache/_app/content/settings.php'; $structure_file = BASE_PATH . '/_cache/_app/content/structure.php'; $time_file = BASE_PATH . '/_cache/_app/content/last.php'; $members_file = BASE_PATH . '/_cache/_app/members/members.php'; $now = time(); // start measuring settings hash $settings_hash = Debug::markStart('caching', 'settings'); // check for current and new settings $settings = unserialize(File::get($settings_file)); if (!is_array($settings)) { $settings = array('site_root' => '', 'site_url' => '', 'timezone' => '', 'date_format' => '', 'time_format' => '', 'content_type' => '', 'taxonomy' => '', 'taxonomy_case_sensitive' => '', 'taxonomy_force_lowercase' => '', 'entry_timestamps' => '', 'base_path' => '', 'app_version' => ''); } // look up current settings $current_settings = array('site_root' => Config::getSiteRoot(), 'site_url' => Config::getSiteURL(), 'timezone' => Config::get('timezone'), 'date_format' => Config::get('date_format'), 'time_format' => Config::get('time_format'), 'content_type' => Config::get('content_type'), 'taxonomy' => Config::getTaxonomies(), 'taxonomy_case_sensitive' => Config::getTaxonomyCaseSensitive(), 'taxonomy_force_lowercase' => Config::getTaxonomyForceLowercase(), 'entry_timestamps' => Config::getEntryTimestamps(), 'base_path' => BASE_PATH, 'app_version' => STATAMIC_VERSION); // have cache-altering settings changed? if ($settings !== $current_settings) { // settings have changed $settings_changed = true; // clear the cache and set current settings $cache = self::getCleanCacheArray(); $settings = $current_settings; $last = null; } else { // grab the existing cache $cache = unserialize(File::get($cache_file)); if (!is_array($cache)) { $cache = self::getCleanCacheArray(); } $last = File::get($time_file); } // mark end of settings hash measuring Debug::markEnd($settings_hash); // grab a list of all content files $files = File::globRecursively(Path::tidy(BASE_PATH . '/' . Config::getContentRoot() . '/*'), Config::getContentType()); // grab a separate list of files that have changed since last check $updated = array(); $current_files = array(); // loop through files, getting local paths and checking for updated files foreach ($files as $file) { $local_file = Path::trimFilesystemFromContent(Path::standardize($file)); // add to current files $current_files[] = $local_file; // is this updated? if ($last && File::getLastModified($file) >= $last) { $updated[] = $local_file; } } // get a diff of files we know about and files currently existing $known_files = array(); foreach ($cache['urls'] as $url_data) { array_push($known_files, $url_data['path']); } $new_files = array_diff($current_files, $known_files); // create a master list of files that need updating $changed_files = array_unique(array_merge($new_files, $updated)); // store a list of changed URLs $changed_urls = array(); // add to the cache if files have been updated if (count($changed_files)) { $files_changed = true; // build content cache foreach ($changed_files as $file) { $file = $full_content_root . $file; $local_path = Path::trimFilesystemFromContent($file); // before cleaning anything, check for hidden or draft content $is_hidden = Path::isHidden($local_path); $is_draft = Path::isDraft($local_path); // now clean up the path $local_filename = Path::clean($local_path); // file parsing $content = substr(File::get($file), 3); $divide = strpos($content, "\n---"); $front_matter = trim(substr($content, 0, $divide)); $content_raw = trim(substr($content, $divide + 4)); // parse data $data = YAML::parse($front_matter); if ($content_raw) { $data['content'] = 'true'; $data['content_raw'] = 'true'; } // set additional information $data['_file'] = $file; $data['_local_path'] = $local_path; $data['_order_key'] = null; $data['datetimestamp'] = null; // legacy $data['datestamp'] = null; $data['date'] = null; $data['time'] = null; $data['numeric'] = null; $data['last_modified'] = filemtime($file); $data['_is_hidden'] = $is_hidden; $data['_is_draft'] = $is_draft; // get initial slug (may be changed below) $data['slug'] = ltrim(basename($file, "." . $content_type), "_"); // folder $instance = $data['slug'] == 'page' ? 1 : 0; $data['_folder'] = Path::clean($data['_local_path']); $slash = Helper::strrpos_count($data['_folder'], '/', $instance); $data['_folder'] = !$slash ? '' : substr($data['_folder'], 1, $slash - 1); $data['_folder'] = !strlen($data['_folder']) ? "/" : $data['_folder']; $data['_basename'] = $data['slug'] . '.' . $content_type; $data['_filename'] = $data['slug']; $data['_is_entry'] = preg_match(Pattern::ENTRY_FILEPATH, $data['_basename']); $data['_is_page'] = preg_match(Pattern::PAGE_FILEPATH, $data['_basename']); // 404 is special if ($data['_local_path'] === "/404.{$content_type}") { $local_filename = $local_path; // order key: date or datetime } elseif (preg_match(Pattern::DATE_OR_DATETIME, $data['_basename'], $matches)) { // order key: date or datetime $date = $matches[1] . '-' . $matches[2] . '-' . $matches[3]; $time = null; if (Config::getEntryTimestamps() && isset($matches[4])) { $time = substr($matches[4], 0, 2) . ":" . substr($matches[4], 2); $date = $date . " " . $time; $data['slug'] = substr($data['slug'], 16); $data['datetimestamp'] = $data['_order_key']; } else { $data['slug'] = substr($data['slug'], 11); } $data['_order_key'] = strtotime($date); $data['datestamp'] = $data['_order_key']; $data['date'] = Date::format(Config::getDateFormat(), $data['_order_key']); $data['time'] = $time ? Date::format(Config::getTimeFormat(), $data['_order_key']) : null; // order key: slug is page, back up a level } elseif ($data['slug'] == 'page' && preg_match(Pattern::NUMERIC, substr($data['_local_path'], Helper::strrpos_count($data['_local_path'], '/', 1)), $matches)) { // order key: slug is page, back up a level $data['_order_key'] = $matches[1]; $data['numeric'] = $data['_order_key']; // order key: numeric } elseif (preg_match(Pattern::NUMERIC, $data['_basename'], $matches)) { // order key: numeric $data['_order_key'] = $matches[1]; $data['numeric'] = $data['_order_key']; $data['slug'] = substr($data['slug'], strlen($matches[1]) + 1); // order key: other } else { // order key: other $data['_order_key'] = $data['_basename']; } // determine url $data['url'] = preg_replace('#/__?#', '/', $local_filename); // remove any content type extensions from the end of filename if (substr($data['url'], -$content_type_length) === '.' . $content_type) { $data['url'] = substr($data['url'], 0, strlen($data['url']) - $content_type_length); } // remove any base pages from filename if (substr($data['url'], -5) == '/page') { $data['url'] = substr($data['url'], 0, strlen($data['url']) - 5); } // add the site root $data['url'] = Path::tidy(Config::getSiteRoot() . $data['url']); // add the site URL to get the permalink $data['permalink'] = Path::tidy(Config::getSiteURL() . $data['url']); // new content if (!isset($cache['content'][$data['_folder']]) || !is_array($cache['content'][$data['_folder']])) { $cache['content'][$data['_folder']] = array(); } $slug_with_extension = $data['_filename'] == 'page' ? substr($data['url'], strrpos($data['url'], '/') + 1) . '/' . $data['_filename'] . "." . $content_type : $data['_filename'] . "." . $content_type; $cache['content'][$data['_folder']][$slug_with_extension] = array('folder' => $data['_folder'], 'path' => $local_path, 'file' => $slug_with_extension, 'url' => $data['url'], 'data' => $data); $cache['urls'][$data['url']] = array('folder' => $data['_folder'], 'path' => $local_path, 'file' => $slug_with_extension); $changed_urls[$data['url']] = true; } } // loop through all cached content for deleted files // this isn't as expensive as you'd think in real-world situations foreach ($cache['content'] as $folder => $folder_contents) { foreach ($folder_contents as $path => $data) { if (File::exists($full_content_root . $data['path'])) { // still here, keep it continue; } $files_changed = true; // get URL $url = isset($cache['content'][$folder][$path]['url']) ? $cache['content'][$folder][$path]['url'] : null; // only remove from URLs list if not in changed URLs list if (!isset($changed_urls[$url]) && !is_null($url)) { // remove from url cache unset($cache['urls'][$url]); } // remove from content cache unset($cache['content'][$folder][$path]); } } // build taxonomy cache // only happens if files were added, updated, or deleted above if ($files_changed) { $taxonomies = Config::getTaxonomies(); $force_lowercase = Config::getTaxonomyForceLowercase(); $case_sensitive = Config::getTaxonomyCaseSensitive(); $cache['taxonomies'] = array(); // rebuild taxonomies if (count($taxonomies)) { // set up taxonomy array foreach ($taxonomies as $taxonomy) { $cache['taxonomies'][$taxonomy] = array(); } // loop through content to build cached array foreach ($cache['content'] as $pages) { foreach ($pages as $item) { $data = $item['data']; // loop through the types of taxonomies foreach ($taxonomies as $taxonomy) { // if this file contains this type of taxonomy if (isset($data[$taxonomy])) { $values = Helper::ensureArray($data[$taxonomy]); // add the file name to the list of found files for a given taxonomy value foreach ($values as $value) { if (!$value) { continue; } $key = !$case_sensitive ? strtolower($value) : $value; if (!isset($cache['taxonomies'][$taxonomy][$key])) { $cache['taxonomies'][$taxonomy][$key] = array('name' => $force_lowercase ? strtolower($value) : $value, 'files' => array()); } array_push($cache['taxonomies'][$taxonomy][$key]['files'], $data['url']); } } } } } } // build structure cache $structure = array(); $home = Path::tidy('/' . Config::getSiteRoot() . '/'); foreach ($cache['content'] as $pages) { foreach ($pages as $item) { // set up base variables $parent = null; // Trim off home and any /page.md ending so that all URLs are treated // equally regardless of page type. $order_key = str_replace('/page.md', '', str_replace($home, '', $item['path'])); $sub_order_key = $item['data']['_order_key']; // does this have a parent (and if so, what is it?) if ($item['url'] !== $home) { $parent = $home; $depth = substr_count(str_replace($home, '/', $item['url']), '/'); $last_slash = strrpos($item['url'], '/', 1); $last_order_slash = strrpos($order_key, '/', 0); if ($last_slash !== false) { $parent = substr($item['url'], 0, $last_slash); } if ($last_order_slash !== false) { $order_key = substr($order_key, 0, $last_order_slash); } if ($item['data']['_is_page']) { $type = $item['data']['slug'] == 'page' ? 'folder' : 'page'; } else { $type = 'entry'; } } else { $depth = 0; $type = 'folder'; $order_key = $home; } $structure[$item['url']] = array('parent' => $parent, 'is_entry' => $item['data']['_is_entry'], 'is_page' => $item['data']['_is_page'], 'is_hidden' => $item['data']['_is_hidden'], 'is_draft' => $item['data']['_is_draft'], 'depth' => $depth, 'order_key' => $order_key ? $order_key : $sub_order_key, 'sub_order_key' => $sub_order_key, 'type' => $type); } } } // mark ending of content cache measuring Debug::markEnd($content_hash); if (!Config::get('disable_member_cache')) { // build member cache // ---------------------------------------------------------------- // start measuring $member_hash = Debug::markStart('caching', 'member'); // grab a list of existing members $users = File::globRecursively(Path::tidy(Config::getConfigPath() . '/users/*'), 'yaml'); // clone for reuse, set up our list of updated users $updated = array(); $current_users = array(); foreach ($users as $user) { $local_file = Path::trimFilesystemFromContent(Path::standardize($user)); // add to current users $current_users[] = $local_file; // is this updated? if ($last && File::getLastModified($user) >= $last) { $updated[] = $local_file; } } // get users from the file $members = unserialize(File::get($members_file)); // get a diff of users we know about and files currently existing $known_users = array(); if (!empty($members)) { foreach ($members as $username => $member_data) { $known_users[$username] = $member_data['_path']; } } // create a master list of users that need updating $changed_users = array_unique(array_merge(array_diff($current_users, $known_users), $updated)); $removed_users = array_diff($known_users, $current_users); if (count($changed_users)) { $members_changed = true; foreach ($changed_users as $user_file) { // file parsing $last_slash = strrpos($user_file, '/') + 1; $last_dot = strrpos($user_file, '.'); $username = substr($user_file, $last_slash, $last_dot - $last_slash); $content = substr(File::get($user_file), 3); $divide = strpos($content, "\n---"); $data = YAML::parse(trim(substr($content, 0, $divide))); $bio_raw = trim(substr($content, $divide + 4)); $data['_path'] = $user_file; if ($bio_raw) { $data['biography'] = 'true'; $data['biography_raw'] = 'true'; } $members[$username] = $data; } } // loop through all cached content for deleted files // this isn't as expensive as you'd think in real-world situations if (!empty($removed_users)) { $members_changed = true; $members = array_diff_key($members, $removed_users); } // mark ending of member cache measuring Debug::markEnd($member_hash); } // write to caches // -------------------------------------------------------------------- // add file-writing to content-cache actions $content_hash = Debug::markStart('caching', 'content'); if ($files_changed) { // store the content cache if (File::put($cache_file, serialize($cache)) === false) { if (!File::isWritable($cache_file)) { Log::fatal('Cache folder is not writable.', 'core', 'content-cache'); } Log::fatal('Could not write to the cache.', 'core', 'content-cache'); return false; } // store the structure cache if (File::put($structure_file, serialize($structure)) === false) { if (!File::isWritable($structure_file)) { Log::fatal('Structure cache file is not writable.', 'core', 'structure-cache'); } Log::fatal('Could not write to the structure cache.', 'core', 'structure-cache'); return false; } } // mark ending of content cache file write measuring Debug::markEnd($content_hash); // add file-writing to settings-cache actions $settings_hash = Debug::markStart('caching', 'settings'); // store the settings cache if ($settings_changed) { if (File::put($settings_file, serialize($settings)) === false) { if (!File::isWritable($settings_file)) { Log::fatal('Settings cache file is not writable.', 'core', 'settings-cache'); } Log::fatal('Could not write to the settings cache file.', 'core', 'settings-cache'); return false; } } // mark ending of settings cache file write measuring Debug::markEnd($settings_hash); if (!Config::get('disable_member_cache')) { // add file-writing to settings-cache actions $member_hash = Debug::markStart('caching', 'member'); // store the members cache if ($members_changed) { if (File::put($members_file, serialize($members)) === false) { if (!File::isWritable($members_file)) { Log::fatal('Member cache file is not writable.', 'core', 'member-cache'); } Log::fatal('Could not write to the member cache file.', 'core', 'member-cache'); return false; } } // mark ending of member cache file write measuring Debug::markEnd($member_hash); } File::put($time_file, $now - 1); return true; }
/** * Populate the email message */ private function populateMessage() { $attributes = $this->attributes; $this->mailer->CharSet = 'UTF-8'; $this->mailer->Subject = $attributes['subject']; $from_parts = Email::explodeEmailString($attributes['from']); $this->mailer->setFrom($from_parts['email'], $from_parts['name']); $to = Helper::ensureArray($this->attributes['to']); foreach ($to as $to_addr) { $to_parts = Email::explodeEmailString($to_addr); $this->mailer->addAddress($to_parts['email'], $to_parts['name']); } if (isset($attributes['cc'])) { $cc = Helper::ensureArray($attributes['cc']); foreach ($cc as $cc_addr) { $cc_parts = Email::explodeEmailString($cc_addr); $this->mailer->addCC($cc_parts['email'], $cc_parts['name']); } } if (isset($attributes['bcc'])) { $bcc = Helper::ensureArray($attributes['bcc']); foreach ($bcc as $bcc_addr) { $bcc_parts = Email::explodeEmailString($bcc_addr); $this->mailer->addBCC($bcc_parts['email'], $bcc_parts['name']); } } if (isset($attributes['html'])) { $this->mailer->msgHTML($attributes['html']); if (isset($attributes['text'])) { $this->mailer->AltBody = $attributes['text']; } } elseif (isset($attributes['text'])) { $this->mailer->msgHTML($attributes['text']); } }
/** * Send an email using a Transactional email service * or native PHP as a fallback. * * @param array $attributes A list of attributes for sending * @return bool on success */ public static function send($attributes = array()) { /* |-------------------------------------------------------------------------- | Required attributes |-------------------------------------------------------------------------- | | We first need to ensure we have the minimum fields necessary to send | an email. | */ $required = array_intersect_key($attributes, array_flip(self::$required)); if (count($required) >= 3) { /* |-------------------------------------------------------------------------- | Load handler from config |-------------------------------------------------------------------------- | | We check the passed data for a mailer + key first, and then fall back | to the global Statamic config. | */ $email_handler = array_get($attributes, 'email_handler', Config::get('email_handler', null)); $email_handler_key = array_get($attributes, 'email_handler_key', Config::get('email_handler_key', null)); if (in_array($email_handler, self::$email_handlers) && $email_handler_key) { /* |-------------------------------------------------------------------------- | Initialize Stampie |-------------------------------------------------------------------------- | | Stampie provides numerous adapters for popular email handlers, such as | Mandrill, Postmark, and SendGrid. Each is written as an abstract | interface in an Adapter Pattern. | */ $mailer = self::initializeEmailHandler($email_handler, $email_handler_key); /* |-------------------------------------------------------------------------- | Initialize Message class |-------------------------------------------------------------------------- | | The message class is an implementation of the Stampie MessageInterface | */ $email = new Message($attributes['to']); /* |-------------------------------------------------------------------------- | Set email attributes |-------------------------------------------------------------------------- | | I hardly think this requires much explanation. | */ $email->setFrom($attributes['from']); $email->setSubject($attributes['subject']); if (isset($attributes['text'])) { $email->setText($attributes['text']); } if (isset($attributes['html'])) { $email->setHtml($attributes['html']); } if (isset($attributes['cc'])) { $email->setCc($attributes['cc']); } if (isset($attributes['bcc'])) { $email->setBcc($attributes['bcc']); } if (isset($attributes['headers'])) { $email->setHeaders($attributes['headers']); } $mailer->send($email); return true; } else { /* |-------------------------------------------------------------------------- | Native PHP Mail |-------------------------------------------------------------------------- | | We're utilizing the popular PHPMailer class to handle the messy | email headers and do-dads. Emailing from PHP in general isn't the best | idea known to man, so this is really a lackluster fallback. | */ try { $email = new PHPMailer(true); // SMTP if ($attributes['smtp'] = array_get($attributes, 'smtp', Config::get('smtp'))) { $email->isSMTP(); if ($smtp_host = array_get($attributes, 'smtp:host', false)) { $email->Host = $smtp_host; } if ($smtp_secure = array_get($attributes, 'smtp:secure', false)) { $email->SMTPSecure = $smtp_secure; } if ($smtp_port = array_get($attributes, 'smtp:port', false)) { $email->Port = $smtp_port; } if (array_get($attributes, 'smtp:auth', false) === TRUE) { $email->SMTPAuth = TRUE; } if ($smtp_username = array_get($attributes, 'smtp:username', false)) { $email->Username = $smtp_username; } if ($smtp_password = array_get($attributes, 'smtp:password', false)) { $email->Password = $smtp_password; } // SENDMAIL } elseif (array_get($attributes, 'sendmail', false)) { $email->isSendmail(); // PHP MAIL } else { $email->isMail(); } $email->CharSet = 'UTF-8'; $from_parts = self::explodeEmailString($attributes['from']); $email->setFrom($from_parts['email'], $from_parts['name']); $to = Helper::ensureArray($attributes['to']); foreach ($to as $to_addr) { $to_parts = self::explodeEmailString($to_addr); $email->addAddress($to_parts['email'], $to_parts['name']); } $email->Subject = $attributes['subject']; if (isset($attributes['html'])) { $email->msgHTML($attributes['html']); if (isset($attributes['text'])) { $email->AltBody = $attributes['text']; } } elseif (isset($attributes['text'])) { $email->msgHTML($attributes['text']); } if (isset($attributes['cc'])) { $cc = Helper::ensureArray($attributes['cc']); foreach ($cc as $cc_addr) { $cc_parts = self::explodeEmailString($cc_addr); $email->addCC($cc_parts['email'], $cc_parts['name']); } } if (isset($attributes['bcc'])) { $bcc = Helper::ensureArray($attributes['bcc']); foreach ($bcc as $bcc_addr) { $bcc_parts = self::explodeEmailString($bcc_addr); $email->addBCC($bcc_parts['email'], $bcc_parts['name']); } } $email->send(); } catch (phpmailerException $e) { echo $e->errorMessage(); //error messages from PHPMailer Log::error($e->errorMessage(), 'core', 'email'); } catch (Exception $e) { echo $e->getMessage(); Log::error($e->getMessage(), 'core', 'email'); } } } return false; }
/** * Target for the member:register_form form * * @return void */ public function member__register() { $referrer = $_SERVER['HTTP_REFERER']; $token = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING); $return = filter_input(INPUT_POST, 'return', FILTER_SANITIZE_STRING); $auto_login = (bool) filter_input(INPUT_POST, 'auto_login', FILTER_SANITIZE_NUMBER_INT); // validate form token if (!$this->tokens->validate($token)) { $this->flash->set('login_error', 'Invalid token.'); URL::redirect($referrer); } // is user logged in? if (Auth::isLoggedIn()) { URL::redirect($return); } // get configurations $allowed_fields = array_get($this->loadConfigFile('fields'), 'fields', array()); // get username $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING); // set up iterators and flags $submission = array('username' => $username); // create member object $member = new Member(array()); // adjust allowed fields to include username and password if (!isset($allowed_fields['username'])) { $allowed_fields['username'] = array(); } if (!isset($allowed_fields['password'])) { $allowed_fields['password'] = array(); } // loop through allowed fields, validating and storing foreach ($allowed_fields as $field => $options) { if (!isset($_POST[$field])) { // field wasn't set, skip it continue; } // set value $value = filter_input(INPUT_POST, $field, FILTER_SANITIZE_STRING); // don't store this value if `save_value` is set to `false` if (array_get($options, 'save_value', true)) { $member->set($field, $value); } // add to submissions, including non-save_value fields because this // is the list that will be validated $submission[$field] = $value; } // ensure UID $member->ensureUID(false); // user-defined validation $errors = $this->tasks->validate($submission); // built-in validation // -------------------------------------------------------------------- // username if (!$username) { $errors['username'] = '******'; } elseif (!Member::isValidUsername($username)) { $errors['username'] = '******'; } elseif (Member::exists($username)) { $errors['username'] = '******'; } // password $password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING); $password_confirm = filter_input(INPUT_POST, 'password_confirmation', FILTER_SANITIZE_STRING); if (empty($password)) { $errors['password'] = '******'; } if (!isset($errors['password']) && !is_null($password_confirm) && $password !== $password_confirm) { $errors['password_confirmation'] = 'Passwords did not match.'; } if (count($errors)) { // errors were found, set a flash message and redirect $this->flash->set('register_error', 'Member not created.'); $this->flash->set('register_field_errors', $errors); // remove password and password_confirm from submission if (isset($submission['password'])) { unset($submission['password']); } if (isset($submission['password_confirmation'])) { unset($submission['password_confirmation']); } $this->flash->set('register_old_values', $submission); // redirect back to the form URL::redirect($referrer); } else { // set new member roles $member->set('roles', Helper::ensureArray($this->fetchConfig('new_member_roles', array(), null, false, false))); if ($this->runHook('pre_process', 'replace', true, $member)) { // save member $member->save(); // trigger a hook $this->runHook('register', 'call', null, $member); // user saved $this->flash->set('register_success', 'Member created.'); if ($auto_login) { Auth::login($username, $password); } // run hook $this->runHook('registration_complete', null, null, $member); // redirect to member home URL::redirect($return); } else { $this->runHook('registration_failure', null, null, $member); $this->flash->set('register_failure', 'Member creation failed.'); } } }
/** * Updates the internal content cache * * @return boolean */ public static function update() { // track if any files have changed $files_changed = false; // grab length of content type extension $content_type = Config::getContentType(); $full_content_root = rtrim(Path::tidy(BASE_PATH . "/" . Config::getContentRoot()), "/"); $content_type_length = strlen($content_type) + 1; // the cache file we'll use $cache_file = BASE_PATH . "/_cache/_app/content/content.php"; $time_file = BASE_PATH . "/_cache/_app/content/last.php"; $now = time(); // grab the existing cache $cache = unserialize(File::get($cache_file)); if (!is_array($cache)) { $cache = array("urls" => array(), "content" => array(), "taxonomies" => array()); } $last = File::get($time_file); // grab a list of all files $finder = new Finder(); $files = $finder->files()->name("*." . Config::getContentType())->in(Config::getContentRoot()); // grab a separate list of files that have changed since last check $updated_files = clone $files; $updated = array(); if ($last) { $updated_files->date(">= " . Date::format("Y-m-d H:i:s", $last)); foreach ($updated_files as $file) { // we don't want directories, they may show up as being modified // if a file inside them has changed or been renamed if (is_dir($file)) { continue; } // this isn't a directory, add it to the list $updated[] = Path::trimFilesystem(Path::standardize($file->getRealPath())); } } // loop over current files $current_files = array(); foreach ($files as $file) { $current_files[] = Path::trimFilesystem(Path::standardize($file->getRealPath())); } // get a diff of files we know about and files currently existing $new_files = array_diff($current_files, $cache['urls']); // create a master list of files that need updating $changed_files = array_unique(array_merge($new_files, $updated)); // add to the cache if files have been updated if (count($changed_files)) { $files_changed = true; // build content cache foreach ($changed_files as $file) { $file = $full_content_root . $file; $local_path = Path::trimFilesystem($file); $local_filename = Path::clean($local_path); // file parsing $content = substr(File::get($file), 3); $divide = strpos($content, "\n---"); $front_matter = trim(substr($content, 0, $divide)); // parse data $data = YAML::parse($front_matter); // set additional information $data['_file'] = $file; $data['_local_path'] = $local_path; $data['_order_key'] = null; $data['datetimestamp'] = null; // legacy $data['datestamp'] = null; $data['date'] = null; $data['time'] = null; $data['numeric'] = null; $data['last_modified'] = filemtime($file); $data['_is_hidden'] = false; $data['_is_draft'] = false; // folder $data['_folder'] = preg_replace(Pattern::ORDER_KEY, "", str_replace($full_content_root, "", $data['_file'])); $slash = strrpos($data['_folder'], "/"); $data['_folder'] = $slash === 0 ? "" : substr($data['_folder'], 1, $slash - 1); // fix hidden/draft files $slug = basename($file, "." . $content_type); if (substr($slug, 0, 2) === "__") { $data['_is_hidden'] = true; $data['slug'] = substr($slug, 2); } elseif (substr($slug, 0, 1) === "_") { $data['_is_draft'] = true; $data['slug'] = substr($slug, 1); } else { $data['slug'] = $slug; } $data['_basename'] = $data['slug'] . "." . $content_type; $data['_filename'] = $data['slug']; $data['_is_entry'] = preg_match(Pattern::ENTRY_FILEPATH, $data['_basename']); $data['_is_page'] = preg_match(Pattern::PAGE_FILEPATH, $data['_basename']); // 404 is special if ($data['_local_path'] === "/404.{$content_type}") { $local_filename = $local_path; // order key } elseif (preg_match(Pattern::DATE_OR_DATETIME, $data['_basename'], $matches)) { $date = $matches[1] . '-' . $matches[2] . '-' . $matches[3]; $time = NULL; if (isset($matches[4])) { $time = substr($matches[4], 0, 2) . ":" . substr($matches[4], 2); $date = $date . " " . $time; $data['slug'] = substr($data['slug'], 16); $data['datetimestamp'] = $data['_order_key']; } else { $data['slug'] = substr($data['slug'], 11); } $data['_order_key'] = strtotime($date); $data['datestamp'] = $data['_order_key']; $data['date'] = Date::format(Config::getDateFormat(), $data['_order_key']); $data['time'] = $time ? Date::format(Config::getTimeFormat(), $data['_order_key']) : NULL; } elseif (preg_match(Pattern::NUMERIC, $data['_basename'], $matches)) { $data['_order_key'] = $matches[1]; $data['numeric'] = $data['_order_key']; $data['slug'] = substr($data['slug'], strlen($matches[1]) + 1); } else { $data['_order_key'] = $data['_basename']; } // determine url $data['url'] = preg_replace("/\\/__?/", "/", $local_filename); // remove any content type extensions from the end of filename if (substr($data['url'], -$content_type_length) === "." . $content_type) { $data['url'] = substr($data['url'], 0, strlen($data['url']) - $content_type_length); } // remove any base pages from filename if (substr($data['url'], -5) == "/page") { $data['url'] = substr($data['url'], 0, strlen($data['url']) - 5); } // add the site root $data['url'] = Path::tidy(Config::getSiteRoot() . $data['url']); // add the site URL to get the permalink $data['permalink'] = Path::tidy(Config::getSiteURL() . $data['url']); // add to cache file $cache['content'][$local_path] = $data; $cache['urls'][$data['url']] = $local_path; } } // loop through all cached content for deleted files // this isn't as expensive as you'd think in real-world situations foreach ($cache['content'] as $local_path => $data) { if (File::exists($full_content_root . $local_path)) { continue; } $files_changed = TRUE; // remove from content cache unset($cache['content'][$local_path]); // remove from url cache $url = array_search($local_path, $cache['urls']); if ($url !== FALSE) { unset($cache['urls'][$url]); } } // build taxonomy cache // only happens if files were added, updated, or deleted above if ($files_changed) { $taxonomies = Config::getTaxonomies(); $force_lowercase = Config::getTaxonomyForceLowercase(); $case_sensitive = Config::getTaxonomyCaseSensitive(); $cache['taxonomies'] = array(); if (count($taxonomies)) { // set up taxonomy array foreach ($taxonomies as $taxonomy) { $cache['taxonomies'][$taxonomy] = array(); } // loop through content to build cached array foreach ($cache['content'] as $file => $data) { // do not grab anything not public if (array_get($data, '_is_hidden', FALSE) || array_get($data, '_is_draft', FALSE)) { continue; } // loop through the types of taxonomies foreach ($taxonomies as $taxonomy) { // if this file contains this type of taxonomy if (isset($data[$taxonomy])) { $values = Helper::ensureArray($data[$taxonomy]); // add the file name to the list of found files for a given taxonomy value foreach ($values as $value) { if (!$value) { continue; } $key = !$case_sensitive ? strtolower($value) : $value; if (!isset($cache['taxonomies'][$taxonomy][$key])) { $cache['taxonomies'][$taxonomy][$key] = array("name" => $force_lowercase ? strtolower($value) : $value, "files" => array()); } array_push($cache['taxonomies'][$taxonomy][$key]['files'], $data['url']); } } } } } if (File::put($cache_file, serialize($cache)) === false) { if (!File::isWritable($cache_file)) { Log::fatal("Cache folder is not writable.", "core", "content-cache"); } Log::fatal("Could not write to the cache.", "core", "content-cache"); return false; } } File::put($time_file, $now); return true; }
/** * Returns HTML to include one or more given $scripts * * @param mixed $scripts Script(s) to create HTML for * @return string */ public function link($scripts) { $files = Helper::ensureArray($scripts); $html = ''; foreach ($files as $file) { $html .= HTML::includeScript($this->get($file)); } return $html; }
/** * Supplements the members in the set * * @param array $context Context for supplementing * @return void */ public function supplement($context = array()) { if ($this->supplemented) { return; } $this->supplemented = true; $context = Helper::ensureArray($context); // determine context $given_context = $context; $context = array('total_found' => isset($given_context['total_found']) ? $given_context['total_found'] : null); // loop through content, supplementing each record with data foreach ($this->members as $data) { // total entries if ($context['total_found']) { $data['total_found'] = (int) $context['total_found']; } } }
/** * Compares two values based on the given $comparison * * @param mixed $allowed Allowed value(s) * @param mixed $test_value Value(s) to check * @param string $comparison Type of comparison to make * @return bool */ public function compareValues($allowed, $test_value, $comparison = "==") { $allowed = Helper::ensureArray($allowed); $test_value = Helper::ensureArray($test_value); $comparison = strtolower($comparison); // loop through each allowed value foreach ($allowed as $allowed_value) { // loop through each test value foreach ($test_value as $sub_test_value) { switch ($comparison) { case "=": case "==": if (strtolower($sub_test_value) == strtolower($allowed_value)) { return true; } break; case "!=": case "<>": case "not": // backwards-from-standard check, returning false if found if (strtolower($sub_test_value) == strtolower($allowed_value)) { return false; } break; case "<": if ($sub_test_value < $allowed_value) { return true; } break; case "<=": if ($sub_test_value <= $allowed_value) { return true; } break; case ">": if ($sub_test_value > $allowed_value) { return true; } break; case ">=": if ($sub_test_value >= $allowed_value) { return true; } break; case "has": case "exists": if (!empty($sub_test_value)) { return true; } break; case "lacks": case "missing": // backwards-from-standard check, returning false if found if (!empty($sub_test_value)) { return false; } break; default: return false; } } } // if we're looking for a negative match (`not` or `lacks`) and we got here, return true if (in_array($comparison, array('!=', '<>', 'not', 'lacks', 'missing'))) { return true; } // in all other cases, return false return false; }
/** * Recursively parses all of the variables in the given text and * returns the parsed text. * * @param string $text Text to parse * @param array|object $data Array or object to use * @param callable $callback Callback function to call * @return string */ public function parseVariables($text, $data, $callback = null) { $this->setupRegex(); // <statamic> // allow avoid tag parsing $noparse = array(); if (isset($data['_noparse'])) { $noparse = \Helper::ensureArray($data['_noparse']); } // </statamic> /** * $data_matches[][0][0] is the raw data loop tag * $data_matches[][0][1] is the offset of raw data loop tag * $data_matches[][1][0] is the data variable (dot notated) * $data_matches[][1][1] is the offset of data variable * $data_matches[][2][0] is the content to be looped over * $data_matches[][2][1] is the offset of content to be looped over */ if (preg_match_all($this->variableLoopRegex, $text, $data_matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE)) { foreach ($data_matches as $match) { // <statamic> // if variable is in the no-parse list, don't parse it $var_name = strpos($match[1][0], '|') !== false ? substr($match[1][0], 0, strpos($match[1][0], '|')) : $match[1][0]; if (in_array($var_name, $noparse)) { $text = $this->createExtraction('noparse', $match[0][0], $match[2][0], $text); continue; } // </statamic> $loop_data = $this->getVariable($match[1][0], $data); if ($loop_data) { $looped_text = ''; $index = 0; // <statamic> // is this data an array? if (is_array($loop_data)) { // is this a list, or simply a set of named variables? if ((bool) count(array_filter(array_keys($loop_data), 'is_string'))) { // this is a set of named variables, don't actually loop over and over, // instead, parse the inner contents with this set's local variables that // have been merged into the bigger scope // merge this local data with callback data before performing actions $loop_value = $loop_data + $data + self::$callbackData; // perform standard actions $str = $this->extractLoopedTags($match[2][0], $loop_value, $callback); $str = $this->parseConditionals($str, $loop_value, $callback); $str = $this->injectExtractions($str, 'looped_tags'); $str = $this->parseVariables($str, $loop_value, $callback); if (!is_null($callback)) { $str = $this->injectExtractions($str, 'callback_blocks'); $str = $this->parseCallbackTags($str, $loop_value, $callback); } $looped_text .= $str; $text = preg_replace('/' . preg_quote($match[0][0], '/') . '/m', addcslashes($looped_text, '\\$'), $text, 1); } else { // this is a list, let's loop $total_results = count($loop_data); foreach ($loop_data as $loop_key => $loop_value) { $index++; // is the value an array? if (!is_array($loop_value)) { // no, make it one $loop_value = array('value' => $loop_value, 'name' => $loop_value); } // set contextual iteration values $loop_value['key'] = $loop_key; $loop_value['index'] = $index; $loop_value['zero_index'] = $index - 1; $loop_value['total_results'] = $total_results; $loop_value['first'] = $index === 1 ? true : false; $loop_value['last'] = $index === $loop_value['total_results'] ? true : false; // merge this local data with callback data before performing actions $loop_value = $loop_value + $data + self::$callbackData; // perform standard actions $str = $this->extractLoopedTags($match[2][0], $loop_value, $callback); $str = $this->parseConditionals($str, $loop_value, $callback); $str = $this->injectExtractions($str, 'looped_tags'); $str = $this->parseVariables($str, $loop_value, $callback); if (!is_null($callback)) { $str = $this->injectExtractions($str, 'callback_blocks'); $str = $this->parseCallbackTags($str, $loop_value, $callback); } $looped_text .= $str; } $text = preg_replace('/' . preg_quote($match[0][0], '/') . '/m', addcslashes($looped_text, '\\$'), $text, 1); } } else { // no, so this is just a value, we're done here return $loop_data; } // </statamic> } else { // It's a callback block. // Let's extract it so it doesn't conflict // with the local scope variables in the next step. $text = $this->createExtraction('callback_blocks', $match[0][0], $match[0][0], $text); } } } /** * $data_matches[0] is the raw data tag * $data_matches[1] is the data variable (dot notated) */ if (preg_match_all($this->variableTagRegex, $text, $data_matches)) { // <statamic> // add ability to specify `or` to find matches foreach ($data_matches[1] as $index => $var) { // <statamic> // check for `or` options if (strpos($var, ' or ') !== false) { $vars = preg_split('/\\s+or\\s+/ms', $var, null, PREG_SPLIT_NO_EMPTY); } else { $vars = array($var); } $size = sizeof($vars); for ($i = 0; $i < $size; $i++) { // <statamic> // account for modifiers $var = trim($vars[$i]); $var_pipe = strpos($var, '|'); $var_name = $var_pipe !== false ? substr($var, 0, $var_pipe) : $var; // </statamic> if (strpos($var, '"') === 0 && strrpos($var, '"') === strlen($var) - 1) { $text = str_replace($data_matches[0][$index], substr($var, 1, strlen($var) - 2), $text); break; } // retrieve the value of $var, otherwise, a no-value string $val = $this->getVariable($var, $data, '__lex_no_value__'); // we only want to keep going if either: // - $val has no value according to the parser // - $val *does* have a value, it's false-y *and* there are multiple options here *and* we're not on the last one if ($val === '__lex_no_value__' || !$val && $size > 1 && $i < $size - 1) { continue; } else { // prevent arrays trying to be printed as a string if (is_array($val)) { $val = ""; \Log::error("Cannot display tag `" . $data_matches[0][$index] . "` because it is a list, not a single value. To display list values, use a tag-pair.", "template", "parser"); } // <statamic> // if variable is in the no-parse list, extract it // handles the very-special |noparse modifier if ($var_pipe !== false && in_array('noparse', array_slice(explode('|', $var), 1)) || in_array($var_name, $noparse)) { $text = $this->createExtraction('noparse', $data_matches[0][$index], $val, $text); } else { // </statamic> $text = str_replace($data_matches[0][$index], $val, $text); // <statamic> } break; } } } // </statamic> } // <statamic> // we need to look for parameters on plain-old non-callback variable tags // right now, this only applies to `format` parameters for dates $regex = '/\\{\\{\\s*(' . $this->variableRegex . ')(\\s+.*?)?\\s*\\}\\}/ms'; if (preg_match_all($regex, $text, $data_matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE)) { foreach ($data_matches as $match) { // grab some starting values & init variables $parameters = array(); $tag = $match[0][0]; $name = $match[1][0]; // is this not the content tag, and is the value known? if ($name != 'content' && isset($data[$name])) { // it is, are there parameters? if (isset($match[2])) { // there are, make a backup of our $data $cb_data = $data; // is $data an array? if (is_array($data)) { // it is, have we had callback data before? if (!empty(self::$callbackData)) { // we have, merge it all together $cb_data = $data + self::$callbackData; } // grab the raw string of parameters $raw_params = $this->injectExtractions($match[2][0], '__cond_str'); // parse them into an array $parameters = $this->parseParameters($raw_params, $cb_data, $callback); } elseif (is_string($data)) { $text = str_replace($tag, $data, $text); } } // check for certain parameters and do what they should do if (isset($parameters['format'])) { $text = str_replace($tag, \Date::format($parameters['format'], $data[$name]), $text); } } } } // </statamic> return $text; }
/** * Parse the error messages out of a fieldset config * * @param $fields array List of fields with their configurations * @return array */ public static function getErrorMessages($fields) { $fields = Helper::ensureArray($fields); $rules = array(); foreach ($fields as $field => $options) { $rules[$field] = $messages = array_get($options, 'error_messages', null); } return $rules; }
/** * Supplements a list of results with first, last, count, and total_results * * @param array $output The data array to supplement * @param array $other_data Other data to add to each record * @return mixed */ public function supplement($output, $other_data = array()) { $i = 1; $length = count($output); $other_data = Helper::ensureArray($other_data); foreach ($output as $key => $item) { $stats = array('first' => $i === 1, 'last' => $i === $length, 'count' => $i, 'total_results' => $length); $output[$key] = $other_data + $stats + $item; $i++; } return $output; }