<?php /* Composr Copyright (c) ocProducts, 2004-2016 See text/EN/licence.txt for full licencing information. NOTE TO PROGRAMMERS: Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes **** */ /** * @license http://opensource.org/licenses/cpal_1.0 Common Public Attribution License * @copyright ocProducts Ltd * @package commandr */ /** * Resource-fs base class. * * @package commandr */ abstract class Resource_fs_base { /* FINDING INFORMATION ABOUT HOOK STRUCTURE */ public $folder_resource_type = null; public $file_resource_type = null; public $_cma_object = array(); /** * Get the file resource info for this Commandr-fs resource hook. * * @param ID_TEXT $resource_type The resource type * @return object The object */ protected function _get_cma_info($resource_type) { if (!array_key_exists($resource_type, $this->_cma_object)) { require_code('content'); $this->_cma_object[$resource_type] = get_content_object($resource_type); } return $this->_cma_object[$resource_type]->info(); } /** * Find whether a resource type is of a folder-type. * * @param ID_TEXT $resource_type The resource type * @return boolean Whether it is */ public function is_folder_type($resource_type) { $folder_types = is_array($this->folder_resource_type) ? $this->folder_resource_type : (is_null($this->folder_resource_type) ? array() : array($this->folder_resource_type)); return in_array($resource_type, $folder_types); } /** * Find whether a resource type is of a file-type. * * @param ID_TEXT $resource_type The resource type * @return boolean Whether it is */ public function is_file_type($resource_type) { $file_types = is_array($this->file_resource_type) ? $this->file_resource_type : (is_null($this->file_resource_type) ? array() : array($this->file_resource_type)); return in_array($resource_type, $file_types); } /* HOOKS MAY OVERRIDE THESE AS REQUIRED, TO ENCODE IMPLEMENTATION COMPLEXITIES */ /** * Whether the filesystem hook is active. * * @return boolean Whether it is */ protected function _is_active() { return true; } /** * Whether the filesystem hook can handle a particular file type. * * @param string $filetype The file type (no file extension) * @return array List of our resource types that can */ public function can_accept_filetype($filetype) { if ($filetype != RESOURCE_FS_DEFAULT_EXTENSION) { return array(); } $ret = array(); if (!is_null($this->folder_resource_type)) { $ret = array_merge($ret, is_array($this->folder_resource_type) ? $this->folder_resource_type : array($this->folder_resource_type)); } if (!is_null($this->folder_resource_type)) { $ret = array_merge($ret, is_array($this->file_resource_type) ? $this->file_resource_type : array($this->file_resource_type)); } return $ret; } /** * Find whether a kind of resource handled by this hook (folder or file) can be under a particular kind of folder. * * @param ?ID_TEXT $above Folder resource type (null: root) * @param ID_TEXT $under Resource type (may be file or folder) * @return ?array A map: The parent referencing field, the table it is in, and the ID field of that table (null: cannot be under) */ protected function _has_parent_child_relationship($above, $under) { $sub_info = $this->_get_cma_info($under); $is_file = $this->is_file_type($under); if ($is_file) { // If no folder types, files are top level if ((is_null($this->folder_resource_type)) && (is_null($above))) { return array( 'cat_field' => null, 'linker_table' => null, 'id_field' => $sub_info['id_field'], 'id_field_linker' => $sub_info['id_field'], 'cat_field_numeric' => null, ); } // If there are folder types, files can not be top level if ((!is_null($this->folder_resource_type)) && (is_null($above))) { return null; } } if (array_key_exists('parent_category_field__resource_fs', $sub_info)) { $sub_info['parent_category_field'] = $sub_info['parent_category_field__resource_fs']; } if (!$is_file) { if ((is_null($sub_info['parent_category_field'])) && (!is_null($sub_info['parent_spec__field_name']))) { // Some fiddling, as we are smart enough to detect need for linker table $sub_info['parent_category_field'] = $sub_info['parent_spec__parent_name']; } } // If there is no category for $under, then it can only be top-level if ((!array_key_exists('parent_category_field', $sub_info)) || (is_null($sub_info['parent_category_field']))) { if (!is_null($above)) { return null; } } $folder_info = is_null($above) ? $sub_info : $this->_get_cma_info($above); return array( 'cat_field' => $sub_info['parent_category_field'], 'linker_table' => $is_file ? null : $sub_info['parent_spec__table_name'], 'id_field' => $sub_info['id_field'], 'id_field_linker' => $is_file ? null : $sub_info['parent_spec__field_name'], 'cat_field_numeric' => $folder_info['id_field_numeric'], ); } /** * Load function for resource-fs (for files). Finds the data for some resource from a resource-fs file. * * @param ID_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) * @return ~string Resource data (false: error) */ public function file_load__flat($filename, $path) { if (array() == $this->can_accept_filetype(get_file_extension($filename))) { return false; } return $this->file_load_json($filename, $path); // By default, only defer to the inbuilt Composr JSON implementation (hooks may override this with support for other kinds of interchange file formats) } /** * Load function for resource-fs (for folders). Finds the data for some resource from a resource-fs folder. * * @param ID_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) * @return ~string Resource data (false: error) */ public function folder_load__flat($filename, $path) { $ext = get_file_extension($filename); if ($ext != '') { if (array() == $this->can_accept_filetype($ext)) { return false; } } return $this->folder_load_json($filename, $path); // By default, only defer to the inbuilt Composr JSON implementation (hooks may override this with support for other kinds of interchange file formats) } /** * Save function for resource-fs (for files). Parses the data for some resource to a resource-fs file. * * @param ID_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) * @param string $data Resource data * @return ~ID_TEXT The resource ID (false: error, could not create via these properties / here) */ public function file_save__flat($filename, $path, $data) { // Files other stuff makes, we don't want auto-created junk files creating composr content $all_disallowed = array( '__macosx', 'thumbs.db:encryptable', 'thumbs.db', '.ds_store', ); foreach ($all_disallowed as $disallowed) { if (strtolower($filename) == $disallowed) { return false; } } if (substr($filename, 0, 1) == '.') { return false; } if (array() == $this->can_accept_filetype(get_file_extension($filename))) { return false; } return $this->file_save_json($filename, $path, $data); // By default, only defer to the inbuilt Composr JSON implementation (hooks may override this with support for other kinds of interchange file formats) } /** * Save function for resource-fs (for folders). Parses the data for some resource to a resource-fs folder. * * @param ID_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) * @param string $data Resource data * @return ~ID_TEXT The resource ID (false: error, could not create via these properties / here) */ public function folder_save__flat($filename, $path, $data) { $ext = get_file_extension($filename); if ($ext != '') { if (array() == $this->can_accept_filetype($ext)) { return false; } } return $this->folder_save_json($filename, $path, $data); // By default, only defer to the inbuilt Composr JSON implementation (hooks may override this with support for other kinds of interchange file formats) } /** * Reinterpret the input of a file, into a way we can understand it to add/edit. Hooks may override this with special import code. * * @param LONG_TEXT $filename Filename OR Resource label * @param string $path The path (blank: root / not applicable) * @param array $properties Properties * @param ID_TEXT $resource_type The resource type * @return array A pair: the resource label, Properties (may be empty, properties given are open to interpretation by the hook but generally correspond to database fields) */ protected function _file_magic_filter($filename, $path, $properties, $resource_type) { $label = basename($filename, '.' . RESOURCE_FS_DEFAULT_EXTENSION); // Default implementation is simply to assume the stub of the filename (or may be a raw label already, with no file type) is the resource label if (array_key_exists('label', $properties)) { $label = $properties['label']; // ...unless the label was explicitly given } $this->_resource_save_extend_pre($properties, $resource_type, $filename, $label); return array($properties, $label); // Leave properties alone } /** * Reinterpret the input of a folder, into a way we can understand it to add/edit. Hooks may override this with special import code. * * @param LONG_TEXT $filename Filename OR Resource label * @param string $path The path (blank: root / not applicable) * @param array $properties Properties * @return array A pair: the resource label, Properties (may be empty, properties given are open to interpretation by the hook but generally correspond to database fields) */ protected function _folder_magic_filter($filename, $path, $properties) { return array($properties, $filename); // Default implementation is simply to assume the filename is the resource label, and leave properties alone } /** * Get the filename for a resource ID. Note that filenames are unique across all folders in a filesystem. * * @param ID_TEXT $resource_type The resource type * @param ID_TEXT $resource_id The resource ID * @return ?ID_TEXT The filename (null: could not find) */ public function file_convert_id_to_filename($resource_type, $resource_id) { $moniker = find_moniker_via_id($resource_type, $resource_id); if (is_null($moniker)) { return null; } return $moniker . '.' . RESOURCE_FS_DEFAULT_EXTENSION; } /** * Get the filename for a resource ID. Note that filenames are unique across all folders in a filesystem. * * @param ID_TEXT $resource_type The resource type * @param ID_TEXT $resource_id The resource ID * @return ?ID_TEXT The filename (null: could not find) */ public function folder_convert_id_to_filename($resource_type, $resource_id) { return find_moniker_via_id($resource_type, $resource_id); } /** * Get the resource ID for a filename (of file). Note that filenames are unique across all folders in a filesystem. * * @param ID_TEXT $filename The filename, or filepath * @param ?ID_TEXT $resource_type The resource type (null: assumption of only one folder resource type for this hook; only passed as non-null from overridden functions within hooks that are calling this as a helper function) * @return ?array A pair: The resource type, the resource ID (null: could not find) */ public function file_convert_filename_to_id($filename, $resource_type = null) { if (is_null($resource_type)) { $resource_type = $this->file_resource_type; } $filename = preg_replace('#^.*/#', '', $filename); // Paths not needed, as filenames are globally unique; paths would not be in alternative_ids table $moniker = basename($filename, '.' . RESOURCE_FS_DEFAULT_EXTENSION); // Remove file extension from filename $resource_id = find_id_via_moniker($resource_type, $moniker); if (is_null($resource_id)) { $resource_id = find_id_via_label($resource_type, $moniker); } return array($resource_type, $resource_id); } /** * Get the resource ID for a filename (of folder). Note that filenames are unique across all folders in a filesystem. * * @param ID_TEXT $filename The filename, or filepath * @param ?ID_TEXT $resource_type The resource type (null: assumption of only one folder resource type for this hook; only passed as non-null from overridden functions within hooks that are calling this as a helper function) * @return array A pair: The resource type, the resource ID */ public function folder_convert_filename_to_id($filename, $resource_type = null) { if (is_null($resource_type)) { $resource_type = $this->folder_resource_type; } $moniker = preg_replace('#^.*/#', '', $filename); // Paths not needed, as filenames are globally unique; paths would not be in alternative_ids table $resource_id = find_id_via_moniker($resource_type, $moniker); return array($resource_type, $resource_id); } /* JUGGLING PROPERTIES */ /** * Find a default property, defaulting to blank. * * @param array $properties The properties * @param ID_TEXT $property The property * @return ?string The value (null: null value) */ protected function _default_property_str($properties, $property) { return array_key_exists($property, $properties) ? $properties[$property] : ''; } /** * Find a default property, defaulting to null. * * @param array $properties The properties * @param ID_TEXT $property The property * @return ?string The value (null: null value) */ protected function _default_property_str_null($properties, $property) { return array_key_exists($property, $properties) ? $properties[$property] : null; } /** * Find an integer default property, defaulting to 0. * * @param array $properties The properties * @param ID_TEXT $property The property * @return ?integer The value (null: null value) */ protected function _default_property_int($properties, $property) { if (!array_key_exists($property, $properties)) { return 0; } if (is_null($properties[$property])) { return 0; } if (is_integer($properties[$property])) { return $properties[$property]; } return intval($properties[$property]); } /** * Find a default property, defaulting to null. * * @param array $properties The properties * @param ID_TEXT $property The property * @return ?integer The value (null: null value) */ protected function _default_property_int_null($properties, $property) { if (!array_key_exists($property, $properties)) { return null; } if (is_null($properties[$property])) { return null; } if (is_integer($properties[$property])) { return $properties[$property]; } return intval($properties[$property]); } /** * Convert a category to an integer, defaulting to null if it is blank. * * @param ?ID_TEXT $category The category value (blank: root) (null: root) * @return ?integer The category (null: root) */ protected function _integer_category($category) { if (is_null($category)) { return null; } return ($category == '') ? null : intval($category); } /** * Find a default property, defaulting to the average of what is there already, or the given default if really necessary. * * @param array $properties The properties * @param ID_TEXT $property The property * @param ID_TEXT $table The table to average within * @param integer $default The last-resort default * @param ?ID_TEXT $db_property The database property (null: same as $property) * @return integer The value */ protected function _default_property_int_modeavg($properties, $property, $table, $default, $db_property = null) { if (is_null($db_property)) { $db_property = $property; } if (array_key_exists($property, $properties)) { if (is_integer($properties[$property])) { return $properties[$property]; } return intval($properties[$property]); } static $cache = array(); if (isset($cache[$property][$table][$default][$db_property])) { return $cache[$property][$table][$default][$db_property]; } $db = $GLOBALS[(substr($table, 0, 2) == 'f_') ? 'FORUM_DB' : 'SITE_DB']; $val = $db->query_value_if_there('SELECT ' . $db_property . ',count(' . $db_property . ') AS qty FROM ' . get_table_prefix() . $table . ' GROUP BY ' . $db_property . ' ORDER BY qty DESC', false, true); // We need the mode here, not the mean $ret = $default; if (!is_null($val)) { $ret = $val; } $cache[$property][$table][$default][$db_property] = $ret; return $ret; } /** * Find a default property for a timestamp, defaulting to null. * * @param array $properties The properties * @param ID_TEXT $property The property * @return ?integer The value (null: null value) */ protected function _default_property_time_null($properties, $property) { if (!isset($properties[$property])) { return null; } return $this->_default_property_time($properties, $property); } /** * Find a default property for a timestamp, defaulting to current time. * * @param array $properties The properties * @param ID_TEXT $property The property * @return integer The value */ protected function _default_property_time($properties, $property) { if (!isset($properties[$property])) { return time(); } if (is_integer($properties[$property])) { return $properties[$property]; } return remap_portable_as_time($properties[$property]); } /** * Find a default property for a member, defaulting to null. * * @param array $properties The properties * @param ID_TEXT $property The property * @return ?integer The value (null: null value) */ protected function _default_property_member_null($properties, $property) { if (!isset($properties[$property])) { return null; } return $this->_default_property_member($properties, $property); } /** * Find a default property for a member. * * @param array $properties The properties * @param ID_TEXT $property The property * @return ?integer The value (null: null value) */ protected function _default_property_member($properties, $property) { if (!isset($properties[$property])) { return get_member(); } if (is_integer($properties[$property])) { return $properties[$property]; } $test = remap_portable_as_resource_id('member', $properties[$property]); if (is_null($test)) { return $test; } return intval($test); } /** * Find a default property for a usergroup, defaulting to null. * * @param array $properties The properties * @param ID_TEXT $property The property * @return ?integer The value (null: null value) */ protected function _default_property_group_null($properties, $property) { if (!isset($properties[$property])) { return null; } return $this->_default_property_group($properties, $property); } /** * Find a default property for a usergroup. * * @param array $properties The properties * @param ID_TEXT $property The property * @return ?integer The value (null: null value) */ protected function _default_property_group($properties, $property) { if (!isset($properties[$property])) { $properties[$property] = db_get_first_id(); } if (is_integer($properties[$property])) { return $properties[$property]; } $test = remap_portable_as_resource_id('group', $properties[$property]); if (is_null($test)) { return $test; } return intval($test); } /** * Find a default property for a URL. * * @param array $properties The properties * @param ID_TEXT $property The property * @param boolean $ignore_conflicts Whether to ignore conflicts with existing files (=edit op, basically) * @return string The value */ protected function _default_property_urlpath($properties, $property, $ignore_conflicts = false) { if (empty($properties[$property])) { return ''; } return remap_portable_as_urlpath($properties[$property], $ignore_conflicts); } /** * Find a default property for a foreign key, defaulting to null. * * @param array $_table_referenced The table the key is to * @param array $properties The properties * @param ID_TEXT $property The property * @return ?mixed The value (null: null value) */ protected function _default_property_foreign_key_null($_table_referenced, $properties, $property) { if (!isset($properties[$property])) { return null; } return $this->_default_property_foreign_key($_table_referenced, $properties, $property); } /** * Find a default property for a foreign key. * * @param array $_table_referenced The table the key is to * @param array $properties The properties * @param ID_TEXT $property The property * @return mixed The value */ protected function _default_property_foreign_key($_table_referenced, $properties, $property) { return remap_portable_as_foreign_key($_table_referenced, $properties[$property]); } /** * Find a default property for a resource, defaulting to null. * * @param ID_TEXT $resource_type The resource type * @param array $properties The properties * @param ID_TEXT $property The property * @return ?mixed The value (null: null value) */ protected function _default_property_resource_id_null($resource_type, $properties, $property) { if (!isset($properties[$property])) { return null; } return $this->_default_property_resource_id($resource_type, $properties, $property); } /** * Find a default property for a resource. * * @param ID_TEXT $resource_type The resource type * @param array $properties The properties * @param ID_TEXT $property The property * @return mixed The value */ protected function _default_property_resource_id($resource_type, $properties, $property) { return remap_portable_as_resource_id($resource_type, $properties[$property]); } /** * Turn a label into a name. * * @param LONG_TEXT $label The label * @return ID_TEXT The name */ protected function _create_name_from_label($label) { $name = strtolower($label); $name = preg_replace('#[^\w\d\.\-]#', '_', $name); $name = preg_replace('#\_+\$#', '', $name); if ($name == '') { $name = 'unnamed'; } require_code('urls2'); $max_moniker_length = intval(get_option('max_moniker_length')); return substr($name, 0, $max_moniker_length); } /** * Helper function: detect if a resource did not save all the properties it was given. * * @param ?ID_TEXT $resource_type The resource type (null: unknown) * @param ~ID_TEXT $resource_id The resource ID (false: was not added/edited) * @param string $path The path (blank: root / not applicable) * @param array $properties Properties */ protected function _log_if_save_matchup($resource_type, $resource_id, $path, $properties) { if ($resource_type === null) { return; // Too difficult to check, don't bother; only expert coding would lead to this scenario anyway } if ($resource_id === false) { return; } global $RESOURCE_FS_LOGGER; if ($RESOURCE_FS_LOGGER === null) { return; // Too much unnecessarily work if the logger is not on } $ok = true; static $similar_ok_before = array(); if ((isset($similar_ok_before[$resource_type][$path])) && ($similar_ok_before[$resource_type][$path] > 10)) { return; } $found_filename = $this->convert_id_to_filename($resource_type, $resource_id); $found_path = $this->search($resource_type, $resource_id, true); if ($found_path !== $path) { resource_fs_logging('Path mismatch for what was saved (actual ' . $found_path . ' vs intended ' . $path . ')', 'warn'); $ok = false; } $actual_properties = $this->resource_load($resource_type, $found_filename, $found_path); foreach (array_keys($properties) as $p) { if (array_key_exists($p, $actual_properties)) { if (str_replace(do_lang('NA'), '', @strval($actual_properties[$p])) != str_replace(do_lang('NA'), '', @strval($properties[$p]))) { resource_fs_logging('Property (' . $p . ') value mismatch for ' . $found_filename . ' (actual ' . str_replace(do_lang('NA'), '', @strval($actual_properties[$p])) . ' vs intended ' . str_replace(do_lang('NA'), '', @strval($properties[$p])) . ').', 'warn'); $ok = false; } } else { resource_fs_logging('Property (' . $p . ') not applicable for ' . $found_filename . '.', 'warn'); $ok = false; } } if ($ok) { if (!isset($similar_ok_before[$resource_type][$path])) { $similar_ok_before[$resource_type][$path] = 0; } $similar_ok_before[$resource_type][$path]++; } } /* ABSTRACT/AGNOSTIC RESOURCE-FS API FOR INTERNAL COMPOSR USE */ /** * Find the foldername/subpath to a resource. * * @param ID_TEXT $resource_type The resource type * @param ID_TEXT $resource_id The resource ID * @param boolean $full_subpath Whether to include the full subpath * @return ?string The foldername/subpath (null: not found) */ public function search($resource_type, $resource_id, $full_subpath = false) { // Find resource require_code('content'); list(, , $cma_info, $content_row) = content_get_details($resource_type, $resource_id, true); if (is_null($content_row)) { return null; } // Okay, exists, but what if no categories for this? if (is_null($this->folder_resource_type)) { return ''; } // For each folder type, see if we can find a position for this resource $cat_resource_types = is_array($this->folder_resource_type) ? $this->folder_resource_type : array($this->folder_resource_type); $cat_resource_types = array_reverse($cat_resource_types); // Need to look from deepest outward, i.e. maximum specificity first $cat_resource_types[] = null; foreach ($cat_resource_types as $cat_resource_type) { $relationship = $this->_has_parent_child_relationship($cat_resource_type, $resource_type); if (is_null($relationship)) { continue; } if (is_null($cat_resource_type)) { return ''; // Exists in root } // Do we need to load up a linker table for getting the category? if ((!is_null($relationship['linker_table'])) && ($cma_info['table'] != $relationship['linker_table'])) { $where = array($relationship['id_field_linker'] => $content_row[$cma_info['id_field']]); $categories = $cma_info['connection']->query_select($relationship['linker_table'], array($relationship['cat_field']), $where); } else { $categories = array($content_row); } foreach ($categories as $category) { // Find category $_category_id = $category[$relationship['cat_field']]; $category_id = is_string($_category_id) ? $_category_id : (is_null($_category_id) ? '' : strval($_category_id)); // Convert category to path $subpath = $this->folder_convert_id_to_filename($cat_resource_type, $category_id); if (is_null($subpath)) { continue; // Weird, some kind of broken category. We'll have to say we cannot find, as it won't be linked into the folder tree. } // Full subpath requested? if ($full_subpath) { $above_subpath = $this->search($cat_resource_type, $category_id, $full_subpath); if ($above_subpath != '') { $subpath = $above_subpath . '/' . $subpath; } } return $subpath; } } return null; } /** * Convert a label to a filename, possibly with auto-creating if needed. This is useful for the Composr-side resource-agnostic API. * * @param LONG_TEXT $label Resource label * @param string $subpath The path (blank: root / not applicable). It may end in "/*" if you want to look for a match under a certain directory * @param ID_TEXT $resource_type Resource type * @param boolean $must_already_exist Whether the content must already exist * @param ?ID_TEXT $use_guid_for_new GUID to auto-create with (null: either not auto-creating, or not specifying the GUID if we are) * @return ?ID_TEXT The filename (null: not found) */ public function convert_label_to_filename($label, $subpath, $resource_type, $must_already_exist = false, $use_guid_for_new = null) { $label = cms_mb_substr($label, 0, 255); $resource_id = $this->convert_label_to_id($label, $subpath, $resource_type, $must_already_exist, $use_guid_for_new); if (is_null($resource_id)) { return null; } return find_commandr_fs_filename_via_id($resource_type, $resource_id); } /** * Convert a label to an ID, possibly with auto-creating if needed. This is useful for the Composr-side resource-agnostic API. * * @param SHORT_TEXT $_label Resource label * @param string $subpath The path (blank: root / not applicable). It may end in "/*" if you want to look for a match under a certain directory * @param ID_TEXT $resource_type Resource type * @param boolean $must_already_exist Whether the content must already exist * @param ?ID_TEXT $use_guid_for_new GUID to auto-create with (null: either not auto-creating, or not specifying the GUID if we are) * @return ?ID_TEXT The ID (null: not found) */ public function convert_label_to_id($_label, $subpath, $resource_type, $must_already_exist = false, $use_guid_for_new = null) { $label = cms_mb_substr($_label, 0, 255); $resource_id = find_id_via_label($resource_type, $label, $subpath); if (is_null($resource_id)) { if (!$must_already_exist) { // Not found, create... resource_fs_logging('Auto-creating an unmatched ' . $resource_type . ' label reference, "' . $_label . '", under "' . $subpath . '"', 'notice'); // Create subpath if ($subpath != '') { if (substr($subpath, -2) == '/*') { $subpath = substr($subpath, 0, strlen($subpath) - 2); } $subpath_bits = explode('/', $subpath); $subpath_above = ''; foreach ($subpath_bits as $i => $subpath_bit) { if (is_array($this->folder_resource_type)) { $folder_resource_type = $this->folder_resource_type[array_key_exists($i, $this->folder_resource_type) ? $i : (count($this->folder_resource_type) - 1)]; } else { $folder_resource_type = $this->folder_resource_type; } list(, $subpath_id) = $this->folder_convert_filename_to_id($subpath_bit); if (is_null($subpath_id)) { // Missing, find via moniker that doesn't match a label due to prefixing if (preg_match('#^[A-Z]+-#', $subpath_bit) != 0) { $_subpath_bit = preg_replace('#^[A-Z]+-#', '', $subpath_bit); $detected_resource_type = strtolower(preg_replace('#-.*$#', '', $subpath_bit)); $subpath_id = find_id_via_label($detected_resource_type, $_subpath_bit, $subpath_above); } } if (is_null($subpath_id)) { // Missing, find via monikerised label $_subpath_bit = $this->_create_name_from_label($subpath_bit); list(, $subpath_id) = $this->folder_convert_filename_to_id($_subpath_bit); } if (is_null($subpath_id)) { // Missing, find via label $subpath_id = find_id_via_label($folder_resource_type, $subpath_bit, $subpath_above); } if (is_null($subpath_id)) { // Still missing, create folder $subpath_id = $this->folder_add($subpath_bit, $subpath_above, array()); } if ($subpath_above != '') { $subpath_above .= '/'; } $subpath_above .= $this->folder_convert_id_to_filename($folder_resource_type, $subpath_id); } } // Create main resource $resource_id = $this->resource_add($resource_type, is_null($_label) ? uniqid('arbitrary', true) : $_label, $subpath, array()); if ($resource_id === false) { return null; } if (!is_null($use_guid_for_new)) { generate_resource_fs_moniker($resource_type, $resource_id, $label, $use_guid_for_new); } } } return $resource_id; } /** * Get the filename for a resource ID (of file or folder). Note that filenames are unique across all folders in a filesystem. * * @param ID_TEXT $resource_type The resource type * @param ID_TEXT $resource_id The resource ID * @return ?ID_TEXT The filename (null: not found) */ public function convert_id_to_filename($resource_type, $resource_id) { if ($this->is_file_type($resource_type)) { return $this->file_convert_id_to_filename($resource_type, $resource_id); } if ($this->is_folder_type($resource_type)) { return $this->folder_convert_id_to_filename($resource_type, $resource_id); } return null; } /** * Get the resource ID for a filename (of file or folder). Note that filenames are unique across all folders in a filesystem. * * @param ID_TEXT $filename The filename, or filepath * @param ID_TEXT $resource_type The resource type * @return ?array A pair: The resource type, the resource ID (null: could not find) */ public function convert_filename_to_id($filename, $resource_type) { if ($this->is_file_type($resource_type)) { return $this->file_convert_filename_to_id($filename, $resource_type); } if ($this->is_folder_type($resource_type)) { return $this->folder_convert_filename_to_id($filename, $resource_type); } return null; } /** * Save function for resource-fs. Parses the data for some resource to a resource-fs JSON file. Wraps file_save/folder_save. * * @param ID_TEXT $resource_type The resource type * @param ID_TEXT $label Filename OR Resource label * @param string $path The path (blank: root / not applicable) * @param ?array $properties Properties (null: none) * @param ?ID_TEXT $search_label_as Whether to look for existing records using $filename as a label and this resource type (null: $filename is a strict file name) * @param ?ID_TEXT $search_path Search path (null: the same as the path saving at) * @return ~ID_TEXT The resource ID (false: error, could not create via these properties / here) */ public function resource_save($resource_type, $label, $path, $properties = null, $search_label_as = null, $search_path = null) { if (is_null($properties)) { $properties = array(); } if ($this->is_folder_type($resource_type)) { $resource_id = $this->folder_save($label, $path, $properties, $search_label_as, $search_path); } else { $resource_id = $this->file_save($label, $path, $properties, $search_label_as, $search_path); } return $resource_id; } /** * Adds some resource with the given label and properties. Wraps file_add/folder_add. * * @param ID_TEXT $resource_type Resource type * @param LONG_TEXT $label Filename OR Resource label * @param string $path The path (blank: root / not applicable) * @param ?array $properties Properties (may be empty, properties given are open to interpretation by the hook but generally correspond to database fields) (null: none) * @return ~ID_TEXT The resource ID (false: error, could not create via these properties / here) */ public function resource_add($resource_type, $label, $path, $properties = null) { if (is_null($properties)) { $properties = array(); } if ($this->is_folder_type($resource_type)) { $resource_id = $this->folder_add($label, $path, $properties, $resource_type); $this->_log_if_save_matchup($resource_type, $resource_id, $path, $properties); } else { $resource_id = $this->file_add($label, $path, $properties, $resource_type); $this->_log_if_save_matchup($resource_type, $resource_id, $path, $properties); } return $resource_id; } /** * Finds the properties for some resource. Wraps file_load/folder_load. * * @param ID_TEXT $resource_type Resource type * @param SHORT_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) * @return ~array Details of the resource (false: error) */ public function resource_load($resource_type, $filename, $path) { if ($this->is_folder_type($resource_type)) { $properties = $this->folder_load($filename, $path); } else { $properties = $this->file_load($filename, $path); } return $properties; } /** * Edits the resource to the given properties. Wraps file_edit/folder_edit. * * @param ID_TEXT $resource_type Resource type * @param ID_TEXT $filename The filename * @param string $path The path (blank: root / not applicable) * @param array $properties Properties (may be empty, properties given are open to interpretation by the hook but generally correspond to database fields) * @param boolean $explicit_move Whether we are definitely moving (as opposed to possible having it in multiple positions) * @return ~ID_TEXT The resource ID (false: error, could not create via these properties / here) */ public function resource_edit($resource_type, $filename, $path, $properties, $explicit_move = false) { if ($this->is_folder_type($resource_type)) { $resource_id = $this->folder_edit($filename, $path, $properties, $explicit_move); $this->_log_if_save_matchup($resource_type, $resource_id, $path, $properties); } else { $resource_id = $this->file_edit($filename, $path, $properties, $explicit_move); $this->_log_if_save_matchup($resource_type, $resource_id, $path, $properties); } return $resource_id; } /** * Deletes the resource. Wraps file_delete/folder_delete. * * @param ID_TEXT $resource_type Resource type * @param ID_TEXT $filename The filename * @param string $path The path (blank: root / not applicable) * @return boolean Success status */ public function resource_delete($resource_type, $filename, $path) { if ($this->is_folder_type($resource_type)) { resource_fs_logging('Deleted the ' . $path . '/' . $filename . ' folder as requested', 'notice'); $status = $this->folder_delete($filename, $path); } else { resource_fs_logging('Deleted the ' . $path . '/' . $filename . ' file as requested', 'notice'); $status = $this->file_delete($filename, $path); } return $status; } /** * Reset resource privileges on the resource for all usergroups. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) */ public function reset_resource_access($filename, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; switch ($resource_type) { case 'comcode_page': list($zone_name, $page_name) = explode(':', $category); $cma_info['connection']->query_delete('group_page_access', array('zone_name' => $zone_name, 'page_name' => $page_name)); $cma_info['connection']->query_delete('member_page_access', array('zone_name' => $zone_name, 'page_name' => $page_name)); break; case 'zone': $cma_info['connection']->query_delete('group_zone_access', array('zone_name' => $category)); $cma_info['connection']->query_delete('member_zone_access', array('zone_name' => $category)); break; default: $cma_info['connection']->query_delete('group_category_access', array('module_the_name' => $module, 'category_name' => $category)); $cma_info['connection']->query_delete('member_category_access', array('module_the_name' => $module, 'category_name' => $category)); break; } } /** * Set resource view access on the resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param array $groups A mapping from group ID to view access * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) */ public function set_resource_access($filename, $groups, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; $admin_groups = $GLOBALS['FORUM_DRIVER']->get_super_admin_groups(); // Cleanup foreach (array_keys($groups) as $group_id) { switch ($resource_type) { case 'comcode_page': list($zone_name, $page_name) = explode(':', $category); $cma_info['connection']->query_delete('group_page_access', array('zone_name' => $zone_name, 'page_name' => $page_name, 'group_id' => $group_id)); break; case 'zone': $cma_info['connection']->query_delete('group_zone_access', array('zone_name' => $category, 'group_id' => $group_id)); break; default: $cma_info['connection']->query_delete('group_category_access', array('module_the_name' => $module, 'category_name' => $category, 'group_id' => $group_id)); break; } } // Insert foreach ($groups as $group_id => $value) { if (in_array($group_id, $admin_groups)) { continue; } if (($value == '1') || ($value == 'true')) { switch ($resource_type) { case 'comcode_page': list($zone_name, $page_name) = explode(':', $category); $cma_info['connection']->query_insert('group_page_access', array('zone_name' => $zone_name, 'page_name' => $page_name, 'group_id' => $group_id), false, true); // Race/corruption condition break; case 'zone': $cma_info['connection']->query_insert('group_zone_access', array('zone_name' => $category, 'group_id' => $group_id), false, true); // Race/corruption condition break; default: $cma_info['connection']->query_insert('group_category_access', array('module_the_name' => $module, 'category_name' => $category, 'group_id' => $group_id), false, true); // Race/corruption condition break; } } } } /** * Get resource view access on the resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) * @return array A mapping from group ID to view access */ public function get_resource_access($filename, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; $admin_groups = $GLOBALS['FORUM_DRIVER']->get_super_admin_groups(); $groups = $GLOBALS['FORUM_DRIVER']->get_usergroup_list(false, true); $ret = array(); foreach (array_keys($groups) as $group_id) { $ret[$group_id] = '0'; } foreach ($admin_groups as $group_id) { $ret[$group_id] = '1'; } switch ($resource_type) { case 'comcode_page': list($zone_name, $page_name) = explode(':', $category); $groups = $cma_info['connection']->query_select('group_zone_access', array('group_id'), array('zone_name' => $zone_name, 'page_name' => $page_name)); break; case 'zone': $groups = $cma_info['connection']->query_select('group_page_access', array('group_id'), array('page_name' => $category)); break; default: $groups = $cma_info['connection']->query_select('group_category_access', array('group_id'), array('module_the_name' => $module, 'category_name' => $category)); break; } foreach ($groups as $group) { $ret[$group['group_id']] = '1'; } return $ret; } /** * Set resource view access on the resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param array $members A mapping from member ID to view access * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) */ public function set_resource_access__members($filename, $members, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; // Cleanup foreach (array_keys($members) as $member_id) { switch ($resource_type) { case 'comcode_page': list($zone_name, $page_name) = explode(':', $category); $cma_info['connection']->query_delete('member_page_access', array('zone_name' => $zone_name, 'page_name' => $page_name, 'member_id' => $member_id, 'active_until' => null)); break; case 'zone': $cma_info['connection']->query_delete('member_zone_access', array('page_name' => $category, 'member_id' => $member_id, 'active_until' => null)); break; default: $cma_info['connection']->query_delete('member_category_access', array('module_the_name' => $module, 'category_name' => $category, 'member_id' => $member_id, 'active_until' => null)); break; } } // Insert foreach ($members as $member_id => $value) { if (($value == '1') || ($value == 'true')) { switch ($resource_type) { case 'comcode_page': list($zone_name, $page_name) = explode(':', $category); $cma_info['connection']->query_insert('member_page_access', array('zone_name' => $zone_name, 'page_name' => $page_name, 'member_id' => $member_id, 'active_until' => null), false, true); // Race/corruption condition break; case 'zone': $cma_info['connection']->query_insert('member_zone_access', array('page_name' => $category, 'member_id' => $member_id, 'active_until' => null), false, true); // Race/corruption condition break; default: $cma_info['connection']->query_insert('member_category_access', array('module_the_name' => $module, 'category_name' => $category, 'member_id' => $member_id, 'active_until' => null), false, true); // Race/corruption condition break; } } } } /** * Get resource view access on the resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) * @return array A mapping from member ID to view access */ public function get_resource_access__members($filename, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; switch ($resource_type) { case 'comcode_page': list($zone_name, $page_name) = explode(':', $category); $members = $cma_info['connection']->query_select('member_page_access', array('member_id'), array('zone_name' => $zone_name, 'page_name' => $page_name, 'active_until' => null)); break; case 'zone': $members = $cma_info['connection']->query_select('member_zone_access', array('member_id'), array('zone_name' => $category, 'active_until' => null)); break; default: $members = $cma_info['connection']->query_select('member_category_access', array('member_id'), array('module_the_name' => $module, 'category_name' => $category, 'active_until' => null)); break; } $ret = array(); foreach ($members as $member) { $ret[$member['member_id']] = '1'; } return $ret; } /** * Reset resource privileges on the resource for all usergroups. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) */ public function reset_resource_privileges($filename, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } if ($resource_type == 'zone') { return; // Can not be done } if ($resource_type == 'comcode_page') { return; // Can not be done } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; $cma_info['connection']->query_delete('group_privileges', array('module_the_name' => $module, 'category_name' => $category)); $cma_info['connection']->query_delete('member_privileges', array('module_the_name' => $module, 'category_name' => $category)); } /** * Work out what a privilege preset means for a kind of resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) * @return ?array A mapping from privilege to minimum preset level required for privilege activation (null: unworkable) */ protected function _compute_privilege_preset_scheme($filename, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } if ($resource_type == 'zone') { return null; // Can not be done } if ($resource_type == 'comcode_page') { return null; // Can not be done } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; $page = $cma_info['cms_page']; require_code('zones2'); $_overridables = extract_module_functions_page(get_module_zone($page), $page, array('get_privilege_overrides')); if (is_null($_overridables[0])) { $overridables = array(); } else { $overridables = is_array($_overridables[0]) ? call_user_func_array($_overridables[0][0], $_overridables[0][1]) : eval($_overridables[0]); } // Work out what privileges we need to work with $privileges_scheme = array(); foreach ($overridables as $override => $cat_support) { $usual_suspects = array('bypass_validation_.*range_content', 'edit_.*range_content', 'edit_own_.*range_content', 'delete_.*range_content', 'delete_own_.*range_content', 'submit_.*range_content'); $access = array(2, 3, 2, 3, 2, 1); // The minimum access level that turns on each of the above permissions NB: Also defined in permissions.js, so keep that in-sync foreach ($usual_suspects as $i => $privilege) { if (preg_match('#' . $privilege . '#', $override) != 0) { $min_level = $access[$i]; $privileges_scheme[$override] = $min_level; } } } return $privileges_scheme; } /** * Set resource privileges from a preset on the resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param array $group_presets A mapping from group ID to preset value. Preset values are 0 (read only) to 3 (moderation) * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) */ public function set_resource_privileges_from_preset($filename, $group_presets, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } $privileges_scheme = $this->_compute_privilege_preset_scheme($filename, $resource_type, $category); if (is_null($privileges_scheme)) { return; } // Set the privileges $group_settings = array(); foreach ($group_presets as $group_id => $level) { $group_settings[$group_id] = array(); foreach ($privileges_scheme as $privilege => $min_level) { $setting = ($level < $min_level) ? '0' : '1'; $group_settings[$group_id][$privilege] = $setting; } } $this->set_resource_privileges($filename, $group_settings); } /** * Set resource privileges on the resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param array $group_settings A map between group ID, and a map of privilege to setting * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) */ public function set_resource_privileges($filename, $group_settings, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } if ($resource_type == 'zone') { return; // Can not be done } if ($resource_type == 'comcode_page') { return; // Can not be done } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; $admin_groups = $GLOBALS['FORUM_DRIVER']->get_super_admin_groups(); // Insert foreach ($group_settings as $group_id => $value) { if (in_array($group_id, $admin_groups)) { continue; } foreach ($value as $privilege => $setting) { if ($setting != '') { $cma_info['connection']->query_delete('group_privileges', array('module_the_name' => $module, 'category_name' => $category, 'group_id' => $group_id, 'privilege' => $privilege, 'the_page' => '')); $cma_info['connection']->query_insert('group_privileges', array('module_the_name' => $module, 'category_name' => $category, 'group_id' => $group_id, 'privilege' => $privilege, 'the_page' => '', 'the_value' => intval($setting)), false, true); // Race/corruption condition } } } } /** * Get the resource privileges for the resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) * @return array A map between group ID, and a map of privilege to setting */ public function get_resource_privileges($filename, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } if ($resource_type == 'zone') { return array(); // Can not be done } if ($resource_type == 'comcode_page') { return array(); // Can not be done } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; $page = $cma_info['cms_page']; require_code('zones2'); $_overridables = extract_module_functions_page(get_module_zone($page), $page, array('get_privilege_overrides')); if (is_null($_overridables[0])) { $overridables = array(); } else { $overridables = is_array($_overridables[0]) ? call_user_func_array($_overridables[0][0], $_overridables[0][1]) : eval($_overridables[0]); } $admin_groups = $GLOBALS['FORUM_DRIVER']->get_super_admin_groups(); $groups = $GLOBALS['FORUM_DRIVER']->get_usergroup_list(false, true); $ret = array(); foreach (array_keys($groups) as $group_id) { $ret[$group_id] = array(); foreach ($overridables as $override => $cat_support) { if ($cat_support) { if (in_array($group_id, $admin_groups)) { $ret[$group_id][$override] = '1'; } else { $ret[$group_id][$override] = '1'; } } } } $groups = $cma_info['connection']->query_select('group_privileges', array('group_id', 'privilege', 'the_value'), array('module_the_name' => $module, 'category_name' => $category, 'the_page' => '')); foreach ($groups as $group) { $ret[$group['group_id']][$group['privilege']] = strval($group['the_value']); } return $ret; } /** * Set resource privileges from a preset so that a member has custom privileges on the resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param array $member_presets A mapping from member ID to preset value. Preset values are 0 (read only) to 3 (moderation) * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) */ public function set_resource_privileges_from_preset__members($filename, $member_presets, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } $privileges_scheme = $this->_compute_privilege_preset_scheme($filename, $resource_type, $category); if (is_null($privileges_scheme)) { return; } // Set the privileges $member_settings = array(); foreach ($member_presets as $member_id => $level) { $member_settings[$member_id] = array(); foreach ($privileges_scheme as $privilege => $min_level) { $setting = ($level < $min_level) ? '0' : '1'; $member_settings[$member_id][$privilege] = $setting; } } $this->set_resource_privileges__members($filename, $member_settings); } /** * Set a resource privilege so that a member has a custom privilege on the resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param array $member_settings A map between member ID, and a map of privilege to setting * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) */ public function set_resource_privileges__members($filename, $member_settings, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } if ($resource_type == 'zone') { return; // Can not be done } if ($resource_type == 'comcode_page') { return; // Can not be done } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; foreach ($member_settings as $member_id => $value) { foreach ($value as $privilege => $setting) { if ($setting != '') { $cma_info['connection']->query_delete('member_privileges', array('module_the_name' => $module, 'category_name' => $category, 'member_id' => $member_id, 'privilege' => $privilege, 'the_page' => '')); $cma_info['connection']->query_insert('member_privileges', array('module_the_name' => $module, 'category_name' => $category, 'member_id' => $member_id, 'privilege' => $privilege, 'the_page' => '', 'the_value' => intval($setting), 'active_until' => null), false, true); // Race/corruption condition } } } } /** * Get the resource privileges for all members that have custom privileges on the resource. * * @param ?ID_TEXT $filename Resource filename (assumed to be of a folder type) (null: $resource_type & $category specified instead) * @param ?ID_TEXT $resource_type The resource type (null: $filename specified instead) * @param ?ID_TEXT $category The resource ID (null: $filename specified instead) * @return array A map between member ID, and a map of privilege to setting */ public function get_resource_privileges__members($filename, $resource_type = null, $category = null) { if ((is_null($filename)) && ((is_null($resource_type)) || (is_null($category)))) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } if (is_null($resource_type)) { list($resource_type, $category) = $this->folder_convert_filename_to_id($filename); } if ($resource_type == 'zone') { return array(); // Can not be done } if ($resource_type == 'comcode_page') { return array(); // Can not be done } $cma_info = $this->_get_cma_info($resource_type); $module = $cma_info['permissions_type_code']; $members = $cma_info['connection']->query_select('member_privileges', array('member_id', 'privilege', 'the_value'), array('module_the_name' => $module, 'category_name' => $category, 'the_page' => '', 'active_until' => null)); $ret = array(); foreach ($members as $member) { $ret[$member['member_id']][$member['privilege']] = strval($member['the_value']); } return $ret; } /* JSON FILE HANDLING: OUR DEFAULT PROPERTY LIST SERIALISATION/DESERIALISATION */ /** * Load function for resource-fs (for files). Finds the data for some resource from a resource-fs JSON file. * * @param ID_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) * @return ~string Resource data (false: error) */ public function file_load_json($filename, $path) { $properties = $this->file_load($filename, $path); if ($properties === false) { return false; } return json_encode($properties); } /** * Load function for resource-fs (for folders). Finds the data for some resource from a resource-fs JSON folder. * * @param ID_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) * @return ~string Resource data (false: error) */ public function folder_load_json($filename, $path) { $properties = $this->folder_load($filename, $path); if ($properties === false) { return false; } return json_encode($properties); } /** * Save function for resource-fs (for files). Parses the data for some resource to a resource-fs JSON file. * * @param ID_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) * @param string $data Resource data * @return ~ID_TEXT The resource ID (false: error, could not create via these properties / here) */ public function file_save_json($filename, $path, $data) { $properties = ($data == '') ? array() : @json_decode($data, true); if ($properties === false) { return false; } return $this->file_save($filename, $path, $properties); } /** * Save function for resource-fs (for files). * * @param ID_TEXT $filename Filename * @param string $path The path to save at (blank: root / not applicable) * @param array $properties Properties * @param ?ID_TEXT $search_label_as Whether to look for existing records using $filename as a label and this resource type (null: $filename is a strict file name) * @param ?ID_TEXT $search_path Search path (null: the same as the path saving at) * @return ~ID_TEXT The resource ID (false: error, could not create via these properties / here) */ public function file_save($filename, $path, $properties, $search_label_as = null, $search_path = null) { if (is_null($search_path)) { $search_path = $path; } $label = $filename; if ($search_label_as !== null) { $filename = $this->convert_label_to_filename($label, $search_path, $search_label_as, true); } if (($GLOBALS['RESOURCE_FS_ADD_ONLY']) && ($filename !== null)) { $resource_id = $this->file_convert_filename_to_id($filename); if ($resource_id !== null) { return $resource_id; } } $existing = mixed(); $existing = ($filename === null) ? false : $this->file_load($filename, $search_path); // NB: Even if it has a wildcard path, it should be acceptable to file_load, as the path is not used for search, only for identifying resource type if ($existing === false) { resource_fs_logging('Added a new ' . $path . '/' . $label . ' file record (i.e. not an edit)', 'inform'); $resource_id = $this->file_add($label, $path, $properties, $search_label_as); $this->_log_if_save_matchup($search_label_as, $resource_id, $path, $properties); return $resource_id; } $resource_id = $this->file_edit($filename, $path, $properties + $existing); $this->_log_if_save_matchup($search_label_as, $resource_id, $path, $properties); return $resource_id; } /** * Save function for resource-fs (for folders). Parses the data for some resource to a resource-fs JSON folder. * * @param ID_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) * @param string $data Resource data * @return ~ID_TEXT The resource ID (false: error, could not create via these properties / here) */ public function folder_save_json($filename, $path, $data) { $properties = @json_decode($data, true); if ($properties === false) { return false; } return $this->folder_save($filename, $path, $properties); } /** * Save function for resource-fs (for folders). * * @param ID_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) * @param array $properties Properties * @param ?ID_TEXT $search_label_as Whether to look for existing records using $filename as a label and this resource type (null: $filename is a strict file name) * @param ?ID_TEXT $search_path Search path (null: the same as the path saving at) * @return ~ID_TEXT The resource ID (false: error, could not create via these properties / here) */ public function folder_save($filename, $path, $properties, $search_label_as = null, $search_path = null) { if (is_null($search_path)) { $search_path = $path; } $label = $filename; if ($search_label_as !== null) { $filename = $this->convert_label_to_filename($label, $search_path, $search_label_as, true); } if (($GLOBALS['RESOURCE_FS_ADD_ONLY']) && ($filename !== null)) { $resource_id = $this->folder_convert_filename_to_id($filename); if ($resource_id !== null) { return $resource_id; } } $existing = mixed(); $existing = ($filename === null) ? false : $this->folder_load($filename, $search_path); // NB: Even if it has a wildcard path, it should be acceptable to file_load, as the path is not used for search, only for identifying resource type if ($existing === false) { resource_fs_logging('Added a new ' . $path . '/' . $label . ' folder record (i.e. not an edit)', 'inform'); $resource_id = $this->folder_add($label, $path, $properties, $search_label_as); $this->_log_if_save_matchup($search_label_as, $resource_id, $path, $properties); return $resource_id; } $resource_id = $this->folder_edit($filename, $path, $properties + $existing); $this->_log_if_save_matchup($search_label_as, $resource_id, $path, $properties); return $resource_id; } /* STANDARD FEATURE INCORPORATION */ /** * Extend a resource with extra properties from standard features a resource type supports. * * @param ID_TEXT $resource_type The resource type * @param ID_TEXT $resource_id The resource ID * @param array $properties Details of properties * @param SHORT_TEXT $filename Filename * @param string $path The path (blank: root / not applicable) */ protected function _resource_load_extend($resource_type, $resource_id, &$properties, $filename, $path) { $cma_info = $this->_get_cma_info($resource_type); $connection = $cma_info['connection']; $reserved_fields = array( 'alternative_ids', 'url_id_monikers', 'attachments', 'content_privacy', 'content_privacy__members', 'content_reviews', 'comments', 'reviews', 'ratings', 'trackbacks', 'access', 'access__members', 'privileges', 'privileges__members', ); if (array_intersect(array_keys($properties), $reserved_fields) != array()) { warn_exit(do_lang_tempcode('INTERNAL_ERROR')); } // Alternative IDs $properties['alternative_ids'] = table_to_portable_rows('alternative_ids', /*skip*/array('resource_moniker', 'resource_label'), array('resource_type' => $resource_type, 'resource_id' => $resource_id), $connection); // URL monikers if ($cma_info['support_url_monikers']) { $page_bits = explode(':', $cma_info['view_page_link_pattern']); $properties['url_id_monikers'] = table_to_portable_rows('url_id_monikers', /*skip*/array('id'), array('m_resource_page' => $page_bits[1], 'm_resource_type' => $page_bits[2], 'm_resource_id' => $resource_id), $connection); } // Attachments if (!is_null($cma_info['attachment_hook'])) { $attachment_refs_rows = collapse_1d_complexity('a_id', $connection->query_select('attachment_refs', array('a_id'), array('r_referer_type' => $cma_info['attachment_hook'], 'r_referer_id' => $resource_id))); $properties['attachments'] = array(); foreach ($attachment_refs_rows as $attachment_id) { $attachment_rows = table_to_portable_rows('attachments', /*skip*/array(), array('id' => $attachment_id), $connection); if (isset($attachment_rows[0])) { $properties['attachments'][] = $attachment_rows[0] + array('_foreign_id' => $attachment_id); } } } // Content privacy if ($cma_info['support_privacy']) { $properties['content_privacy'] = table_to_portable_rows('content_privacy', /*skip*/array(), array('content_type' => $resource_type, 'content_id' => $resource_id), $connection); $properties['content_privacy__members'] = table_to_portable_rows('content_privacy__members', /*skip*/array(), array('content_type' => $resource_type, 'content_id' => $resource_id), $connection); } // Content reviews (by staff) if ($cma_info['support_content_reviews'] && addon_installed('content_reviews')) { $properties['content_reviews'] = table_to_portable_rows('content_reviews', /*skip*/array(), array('content_type' => $resource_type, 'content_id' => $resource_id), $connection); } if (!is_null($cma_info['feedback_type_code'])) { if (get_forum_type() == 'cns') { // Comments & Reviews require_code('feedback'); $topic_id = $GLOBALS['FORUM_DRIVER']->find_topic_id_for_topic_identifier(find_overridden_comment_forum($cma_info['feedback_type_code']), $cma_info['feedback_type_code'] . '_' . $resource_id); if (!is_null($topic_id)) { $comments = get_resource_fs_record('topic', strval($topic_id)); if (!is_null($comments)) { $properties['comments'] = json_decode($comments[0], true); $properties['comments']['posts'] = array(); $posts = $GLOBALS['FORUM_DB']->query_select('f_posts', array('id'), array('p_topic_id' => $topic_id), 'ORDER BY p_time ASC,id ASC'); foreach ($posts as $_post) { $post = get_resource_fs_record('post', strval($_post['id'])); $properties['comments']['posts'][] = json_decode($post[0], true); } } } $properties['reviews'] = table_to_portable_rows('review_supplement', /*skip*/array('id'), array('r_rating_type' => $cma_info['feedback_type_code'], 'r_rating_for_id' => $resource_id), $connection); // NB: r_topic_id and r_post_id will automatically be made portable, so associated with the correct comment } // Ratings $properties['ratings'] = table_to_portable_rows('rating', /*skip*/array('id'), array('rating_for_type' => $cma_info['feedback_type_code'], 'rating_for_id' => $resource_id), $connection); // Trackbacks $properties['trackbacks'] = table_to_portable_rows('trackbacks', /*skip*/array('id'), array('trackback_for_type' => $cma_info['feedback_type_code'], 'trackback_for_id' => $resource_id), $connection); } // Custom fields if ($cma_info['support_custom_fields']) { $properties += $this->_custom_fields_load($resource_type, $resource_id); } // Permissions if (!is_null($cma_info['permissions_type_code']) && $cma_info['is_category'] && $cma_info['category_field'] == $cma_info['id_field']) { $properties['access'] = $this->get_resource_access(null, $resource_type, $resource_id); $properties['access__members'] = $this->get_resource_access__members(null, $resource_type, $resource_id); $properties['privileges'] = $this->get_resource_privileges(null, $resource_type, $resource_id); $properties['privileges__members'] = $this->get_resource_privileges__members(null, $resource_type, $resource_id); } // Properties not used for anything, but interesting $properties['comment__resource_type'] = $resource_type; $properties['comment__resource_id'] = $resource_id; $properties['comment__path'] = $path; $properties['comment__filename'] = $filename; //$properties['comment__generation_time'] = remap_time_as_portable(time()); Would break git history if (isset($properties['edit_date'])) { $properties['comment__edit_date_note'] = 'You may remove the edit_date if you want it to auto-generate to the current-time when saving'; } } /** * Modify standard properties as may be needed by implications of extra properties. * * @param array $properties Details of properties (returned by reference) * @param ID_TEXT $resource_type The resource type * @param ID_TEXT $filename Filename * @param LONG_TEXT $label Resource label */ protected function _resource_save_extend_pre(&$properties, $resource_type, $filename, $label) { $cma_info = $this->_get_cma_info($resource_type); $connection = $cma_info['connection']; // New Attachment IDs need generating and substituting if (!is_null($cma_info['attachment_hook'])) { if (isset($properties['attachments'])) { $new_id = $connection->query_select_value_if_there('attachments', 'MAX(id)'); if (is_null($new_id)) { $new_id = db_get_first_id(); } else { $new_id++; } $attachments = &$properties['attachments']; foreach ($attachments as &$attachment) { $foreign_id = $attachment['_foreign_id']; foreach ($properties as &$val) { if (is_string($val)) { $val = preg_replace('#(\[attachment( .*)?\])' . strval($foreign_id) . '(\[/attachment\])#U', '$1' . strval($new_id) . '$3', $val); $val = preg_replace('#(\[attachment_safe( .*)?\])' . strval($foreign_id) . '(\[/attachment_safe\])#U', '$1' . strval($new_id) . '$3', $val); } } $attachment['_new_id'] = $new_id; $new_id++; } } } } /** * Save extra properties from standard features a resource type supports. * * @param ID_TEXT $resource_type The resource type * @param ID_TEXT $resource_id The resource ID * @param ID_TEXT $filename Filename * @param LONG_TEXT $label Resource label * @param array $properties Details of properties */ protected function _resource_save_extend($resource_type, $resource_id, $filename, $label, $properties) { $cma_info = $this->_get_cma_info($resource_type); $connection = $cma_info['connection']; // Alternative IDs if (isset($properties['alternative_ids'])) { foreach ($properties['alternative_ids'] as &$alternative_id) { $alternative_id['resource_moniker'] = basename($filename, '.' . RESOURCE_FS_DEFAULT_EXTENSION); $alternative_id['resource_label'] = $label; } table_from_portable_rows('alternative_ids', $properties['alternative_ids'], array('resource_type' => $resource_type, 'resource_id' => $resource_id), TABLE_REPLACE_MODE_BY_EXTRA_FIELD_DATA, $connection); } // URL monikers if ($cma_info['support_url_monikers']) { if (isset($properties['url_id_monikers'])) { $page_bits = explode(':', $cma_info['view_page_link_pattern']); table_from_portable_rows('url_id_monikers', $properties['url_id_monikers'], array('m_resource_page' => $page_bits[1], 'm_resource_type' => $page_bits[2], 'm_resource_id' => $resource_id), TABLE_REPLACE_MODE_BY_EXTRA_FIELD_DATA, $connection); } } // Attachments if (!is_null($cma_info['attachment_hook'])) { if (isset($properties['attachments'])) { $attachments = $properties['attachments']; // Delete old attachments require_code('attachments3'); delete_comcode_attachments($cma_info['attachment_hook'], $resource_id, $connection, true); // Metadata $db_fields = collapse_2d_complexity('m_name', 'm_type', $connection->query_select('db_meta', array('m_name', 'm_type'), array('m_table' => 'attachments'))); $relation_map = get_relation_map_for_table('attachments'); // Insert new attachments foreach ($attachments as $attachment) { $foreign_id = $attachment['_foreign_id']; unset($attachment['_foreign_id']); $new_attachment_id = $attachment['_new_id']; unset($attachment['_new_id']); $attachment_row = table_row_from_portable_row($attachment, $db_fields, $relation_map); $attachment_row['id'] = $new_attachment_id; $connection->query_insert('attachments', $attachment_row); $connection->query_insert('attachment_refs', array('r_referer_type' => $cma_info['attachment_hook'], 'r_referer_id' => $resource_id, 'a_id' => $new_attachment_id)); } } } // Content privacy if ($cma_info['support_privacy']) { if (isset($properties['content_privacy'])) { table_from_portable_rows('content_privacy', $properties['content_privacy'], array('content_type' => $resource_type, 'content_id' => $resource_id), TABLE_REPLACE_MODE_BY_EXTRA_FIELD_DATA, $connection); } if (isset($properties['content_privacy__members'])) { table_from_portable_rows('content_privacy__members', $properties['content_privacy__members'], array('content_type' => $resource_type, 'content_id' => $resource_id), TABLE_REPLACE_MODE_BY_EXTRA_FIELD_DATA, $connection); } } // Content reviews (by staff) if ($cma_info['support_content_reviews']) { if (isset($properties['content_reviews'])) { table_from_portable_rows('content_reviews', $properties['content_reviews'], array('content_type' => $resource_type, 'content_id' => $resource_id), TABLE_REPLACE_MODE_BY_EXTRA_FIELD_DATA, $connection); } } if (!is_null($cma_info['feedback_type_code'])) { if (get_forum_type() == 'cns') { // Comments & Reviews if (isset($properties['comments'])) { $comments = $properties['comments']; $comments['description'] = preg_replace('#^(.*: ) .*$#', '$1 ' . $cma_info['feedback_type_code'] . '_' . $resource_id, $comments['description']); $forum_name = find_overridden_comment_forum($cma_info['feedback_type_code']); require_code('feedback'); $topic_id = $GLOBALS['FORUM_DRIVER']->find_topic_id_for_topic_identifier($forum_name, $cma_info['feedback_type_code'] . '_' . $resource_id); if (is_null($topic_id)) { $forum_id = $GLOBALS['FORUM_DRIVER']->forum_id_from_name($forum_name); $resource_fs_path = $comments['comment__path'] . '/' . $comments['comment__filename']; } else { $resource_fs_path = find_commandr_fs_filename_via_id('topic', strval($topic_id), true); } // Save topic $resource_fs_ob = get_resource_commandr_fs_object('topic'); $_topic_id = $resource_fs_ob->resource_save('topic', basename($resource_fs_path), dirname($resource_fs_path), $comments); if ($_topic_id === false) { fatal_exit(do_lang_tempcode('INTERNAL_ERROR')); } $resource_fs_path_topic = find_commandr_fs_filename_via_id('topic', $_topic_id, true); // Save each post $resource_fs_ob = get_resource_commandr_fs_object('post'); foreach ($comments['posts'] as $post) { $resource_fs_path_post = $resource_fs_path_topic . '/' . $post['comment__filename']; $test = $resource_fs_ob->resource_save('post', basename($resource_fs_path_post), dirname($resource_fs_path_post), $post); if ($test === false) { fatal_exit(do_lang_tempcode('INTERNAL_ERROR')); } } } if (isset($properties['reviews'])) { table_from_portable_rows('review_supplement', $properties['reviews'], array('r_rating_type' => $cma_info['feedback_type_code'], 'r_rating_for_id' => $resource_id), TABLE_REPLACE_MODE_BY_EXTRA_FIELD_DATA, $connection); } } // Ratings if (isset($properties['ratings'])) { table_from_portable_rows('rating', $properties['ratings'], array('rating_for_type' => $cma_info['feedback_type_code'], 'rating_for_id' => $resource_id), TABLE_REPLACE_MODE_BY_EXTRA_FIELD_DATA, $connection); } // Trackbacks if (isset($properties['trackbacks'])) { table_from_portable_rows('trackbacks', $properties['trackbacks'], array('trackback_for_type' => $cma_info['feedback_type_code'], 'trackback_for_id' => $resource_id), TABLE_REPLACE_MODE_BY_EXTRA_FIELD_DATA, $connection); } } // Custom fields if ($cma_info['support_custom_fields']) { $this->_custom_fields_save($resource_type, $resource_id, $filename, $label, $properties); } // Permissions if (!is_null($cma_info['permissions_type_code']) && $cma_info['is_category'] && $cma_info['category_field'] == $cma_info['id_field']) { if (isset($properties['access'])) { $groups = $properties['access']; $this->set_resource_access(null, $groups, $resource_type, $resource_id); } if (isset($properties['access__members'])) { $members = $properties['access__members']; $this->set_resource_access__members(null, $members, $resource_type, $resource_id); } if (isset($properties['privileges'])) { $group_settings = $properties['privileges']; $this->set_resource_privileges(null, $group_settings, $resource_type, $resource_id); } if (isset($properties['privileges__members'])) { $member_settings = $properties['privileges__members']; $this->set_resource_privileges__members(null, $member_settings, $resource_type, $resource_id); } } } /** * Find details of custom properties. * * @param ID_TEXT $type The resource type * @return array Details of properties */ protected function _custom_fields_enumerate_properties($type) { static $cache = array(); if (array_key_exists($type, $cache)) { return $cache[$type]; } $cma_info = $this->_get_cma_info($type); $connection = $cma_info['connection']; require_code('fields'); if (!has_tied_catalogue($type)) { return array(); } $props = array(); $fields = get_catalogue_fields('_' . $type); foreach ($fields as $field_bits) { $cf_name = get_translated_text($field_bits['cf_name'], $connection); $fixed_id = 'custom__' . fix_id($cf_name); if (!array_key_exists($fixed_id, $props)) { $key = $fixed_id; } else { $key = 'custom__field_' . strval($field_bits['id']); } require_code('fields'); $ob = get_fields_hook($field_bits['cf_type']); list(, , $storage_type) = $ob->get_field_value_row_bits(array('id' => null, 'cf_type' => $field_bits['cf_type'], 'cf_default' => '')); $_type = 'SHORT_TEXT'; switch ($storage_type) { case 'short_trans': $_type = 'SHORT_TRANS'; break; case 'long_trans': $_type = 'LONG_TRANS'; break; case 'long': $_type = 'LONG_TEXT'; break; case 'integer': $_type = 'INTEGER'; break; case 'float': $_type = 'REAL'; break; } $props[$key] = $_type; } $cache[$type] = $props; return $props; } /** * Load custom properties. * * @param ID_TEXT $type The resource type * @param ID_TEXT $id The content ID * @return array Loaded properties */ protected function _custom_fields_load($type, $id) { require_code('fields'); if (!has_tied_catalogue($type)) { return array(); } $cma_info = $this->_get_cma_info($type); $connection = $cma_info['connection']; $properties = array(); require_code('catalogues'); $catalogue_entry_id = get_bound_content_entry($type, $id); if (!is_null($catalogue_entry_id)) { $special_fields = get_catalogue_entry_field_values('_' . $type, $catalogue_entry_id); } else { $special_fields = $connection->query_select('catalogue_fields', array('*'), array('c_name' => '_' . $type), 'ORDER BY cf_order,' . $GLOBALS['FORUM_DB']->translate_field_ref('cf_name')); } $prop_names = array_keys($this->_custom_fields_enumerate_properties($type)); foreach ($special_fields as $i => $field) { $default = $field['cf_default']; if (array_key_exists('effective_value_pure', $field)) { $default = $field['effective_value_pure']; } elseif (array_key_exists('effective_value', $field)) { $default = $field['effective_value']; } $prop_name = $prop_names[$i]; $properties[$prop_name] = $default; } return $properties; } /** * Save custom properties. * * @param ID_TEXT $type The resource type * @param ID_TEXT $id The content ID * @param ID_TEXT $filename Filename * @param LONG_TEXT $label Resource label * @param array $properties Properties to save */ protected function _custom_fields_save($type, $id, $filename, $label, $properties) { require_code('fields'); if (!has_tied_catalogue($type)) { return; } $cma_info = $this->_get_cma_info($type); $connection = $cma_info['connection']; $existing = get_bound_content_entry($type, $id); require_code('catalogues'); // Get field values $fields = $connection->query_select('catalogue_fields', array('*'), array('c_name' => '_' . $type), 'ORDER BY cf_order,' . $GLOBALS['FORUM_DB']->translate_field_ref('cf_name')); $map = array(); require_code('fields'); $prop_names = array_keys($this->_custom_fields_enumerate_properties($type)); foreach ($fields as $i => $field) { $prop_name = $prop_names[$i]; if (!array_key_exists($prop_name, $properties)) { $properties[$prop_name] = ''; } $map[$field['id']] = $properties[$prop_name]; } $first_cat = $connection->query_select_value('catalogue_categories', 'MIN(id)', array('c_name' => '_' . $type)); require_code('catalogues2'); if (!is_null($existing)) { actual_edit_catalogue_entry($existing, $first_cat, 1, '', 0, 0, 0, $map); } else { $catalogue_entry_id = actual_add_catalogue_entry($first_cat, 1, '', 0, 0, 0, $map); $connection->query_insert('catalogue_entry_linkage', array( 'catalogue_entry_id' => $catalogue_entry_id, 'content_type' => $type, 'content_id' => $id, )); } } /* COMMANDR-FS BINDING */ /** * Standard Commandr-fs listing function for Commandr-fs hooks. * * @param array $meta_dir The current meta-directory path * @param string $meta_root_node The root node of the current meta-directory * @param object $commandr_fs A reference to the Commandr filesystem object * @return ~array The final directory listing (false: failure) */ public function listing($meta_dir, $meta_root_node, &$commandr_fs) { if (!$this->_is_active()) { return false; } $listing = array(); $folder_types = is_array($this->folder_resource_type) ? $this->folder_resource_type : (is_null($this->folder_resource_type) ? array() : array($this->folder_resource_type)); $file_types = is_array($this->file_resource_type) ? $this->file_resource_type : (is_null($this->file_resource_type) ? array() : array($this->file_resource_type)); // Find where we're at $cat_id = ''; $cat_resource_type = mixed(); if (count($meta_dir) != 0) { if (is_null($this->folder_resource_type)) { return false; // Should not be possible } list($cat_resource_type, $cat_id) = $this->folder_convert_filename_to_id(implode('/', $meta_dir)); } // Find folders foreach ($folder_types as $resource_type) { $relationship = $this->_has_parent_child_relationship($cat_resource_type, $resource_type); if (is_null($relationship)) { continue; } $_cat_id = ($relationship['cat_field_numeric'] ? (($cat_id == '') ? null : intval($cat_id)) : $cat_id); $folder_info = $this->_get_cma_info($resource_type); $select = array('main.*'); $table = $folder_info['table'] . ' main'; if ((!is_null($relationship['linker_table'])) && ($relationship['linker_table'] != $folder_info['table'])) { if ((!is_null($_cat_id)) && ($_cat_id !== '')) { $table = $folder_info['table'] . ' main JOIN ' . $folder_info['connection']->get_table_prefix() . $relationship['linker_table'] . ' cats ON cats.' . $relationship['id_field_linker'] . '=main.' . $relationship['id_field']; } } if (!is_null($folder_info['add_time_field'])) { $select[] = 'main.' . $folder_info['add_time_field']; } if (!is_null($folder_info['edit_time_field'])) { $select[] = 'main.' . $folder_info['edit_time_field']; } if (!is_array($folder_info['id_field'])) { $select[] = 'main.' . $folder_info['id_field']; } $extra = ''; if (can_arbitrary_groupby()) { $extra .= 'GROUP BY main.' . $relationship['id_field'] . ' '; // In case it's not a real category table, just an implied one by self-categorisation of entries } $extra .= 'ORDER BY main.' . $relationship['id_field']; if (is_null($relationship['cat_field'])) { $where = array(); } else { if (((is_null($_cat_id)) || ($_cat_id === '')) && ($relationship['linker_table'] != $folder_info['table'])) { $where = array($relationship['id_field'] => ($folder_info['id_field_numeric'] ? db_get_first_id() : '')); // Don't go through the linker table for the root category } else { $where = array($relationship['cat_field'] => $_cat_id); } } $select = array_unique($select); $child_folders = $folder_info['connection']->query_select($table, $select, $where, $extra, 10000/*Reasonable limit*/); foreach ($child_folders as $folder) { $str_id = extract_content_str_id_from_data($folder, $folder_info); $filename = $this->folder_convert_id_to_filename($resource_type, $str_id); $filetime = mixed(); if (method_exists($this, '_get_folder_edit_date')) { $filetime = $this->_get_folder_edit_date($folder, end($meta_dir)); } if (is_null($filetime)) { if (!is_null($folder_info['edit_time_field'])) { $filetime = $folder[$folder_info['edit_time_field']]; } if (is_null($filetime)) { if (!is_null($folder_info['add_time_field'])) { $filetime = $folder[$folder_info['add_time_field']]; } } } $listing[] = array( $filename, COMMANDR_FS_DIR, null/*don't calculate a filesize*/, $filetime, ); } } // Find files foreach ($file_types as $resource_type) { $relationship = $this->_has_parent_child_relationship($cat_resource_type, $resource_type); if (is_null($relationship)) { continue; } $file_info = $this->_get_cma_info($resource_type); $where = array(); if (!is_null($this->folder_resource_type)) { $_cat_id = ($relationship['cat_field_numeric'] ? (($cat_id == '') ? null : intval($cat_id)) : $cat_id); $where[$relationship['cat_field']] = $_cat_id; } $select = array(); append_content_select_for_id($select, $file_info); if (!is_null($file_info['add_time_field'])) { $select[] = $file_info['add_time_field']; } if (!is_null($file_info['edit_time_field'])) { $select[] = $file_info['edit_time_field']; } if (!is_array($file_info['id_field'])) { $select[] = $file_info['id_field']; } $select = array_unique($select); $files = $file_info['connection']->query_select($file_info['table'], $select, $where, '', 10000/*Reasonable limit*/); foreach ($files as $file) { $str_id = extract_content_str_id_from_data($file, $file_info); $filename = $this->file_convert_id_to_filename($resource_type, $str_id); $filetime = mixed(); if (method_exists($this, '_get_file_edit_date')) { $filetime = $this->_get_file_edit_date($file, end($meta_dir)); } if (is_null($filetime)) { if (!is_null($file_info['edit_time_field'])) { $filetime = $file[$file_info['edit_time_field']]; } if (is_null($filetime)) { if (!is_null($file_info['add_time_field'])) { $filetime = $file[$file_info['add_time_field']]; } } } $listing[] = array( $filename, COMMANDR_FS_FILE, null/*don't calculate a filesize*/, $filetime, ); } } if ($cat_id != '') { // File for editing the folder's own properties list($cat_resource_type, $cat_id) = $this->folder_convert_filename_to_id(implode('/', $meta_dir)); require_code('content'); $folder_info = $this->_get_cma_info($cat_resource_type); $folder = content_get_row($cat_id, $folder_info); $filetime = mixed(); if (method_exists($this, '_get_file_edit_date')) { $filetime = $this->_get_folder_edit_date($folder, end($meta_dir)); } if (is_null($filetime)) { if (!is_null($folder_info['edit_time_field'])) { $filetime = $folder[$folder_info['edit_time_field']]; } if (is_null($filetime)) { if (!is_null($folder_info['add_time_field'])) { $filetime = $folder[$folder_info['add_time_field']]; } } } $listing[] = array( RESOURCE_FS_SPECIAL_DIRECTORY_FILE, COMMANDR_FS_FILE, null/*don't calculate a filesize*/, $filetime, ); } return $listing; } /** * Standard Commandr-fs directory creation function for Commandr-fs hooks. * * @param array $meta_dir The current meta-directory path * @param string $meta_root_node The root node of the current meta-directory * @param string $new_dir_name The new directory name * @param object $commandr_fs A reference to the Commandr filesystem object * @return boolean Success? */ public function make_directory($meta_dir, $meta_root_node, $new_dir_name, &$commandr_fs) { if (is_null($this->folder_resource_type)) { return false; } return $this->folder_add($new_dir_name, implode('/', $meta_dir), array()); } /** * Standard Commandr-fs directory removal function for Commandr-fs hooks. * * @param array $meta_dir The current meta-directory path * @param string $meta_root_node The root node of the current meta-directory * @param string $dir_name The directory name * @param object $commandr_fs A reference to the Commandr filesystem object * @return boolean Success? */ public function remove_directory($meta_dir, $meta_root_node, $dir_name, &$commandr_fs) { if (is_null($this->folder_resource_type)) { return false; } return $this->folder_delete($dir_name, implode('/', $meta_dir)); } /** * Standard Commandr-fs file reading function for Commandr-fs hooks. * * @param array $meta_dir The current meta-directory path * @param string $meta_root_node The root node of the current meta-directory * @param string $file_name The file name * @param object $commandr_fs A reference to the Commandr filesystem object * @return ~string The file contents (false: failure) */ public function read_file($meta_dir, $meta_root_node, $file_name, &$commandr_fs) { if ($file_name == RESOURCE_FS_SPECIAL_DIRECTORY_FILE) { return $this->folder_load__flat(array_pop($meta_dir), implode('/', $meta_dir)); } return $this->file_load__flat($file_name, implode('/', $meta_dir)); } /** * Standard Commandr-fs file writing function for Commandr-fs hooks. * * @param array $meta_dir The current meta-directory path * @param string $meta_root_node The root node of the current meta-directory * @param string $file_name The file name * @param string $contents The new file contents * @param object $commandr_fs A reference to the Commandr filesystem object * @return boolean Success? */ public function write_file($meta_dir, $meta_root_node, $file_name, $contents, &$commandr_fs) { if ($file_name == RESOURCE_FS_SPECIAL_DIRECTORY_FILE) { return $this->folder_save__flat(array_pop($meta_dir), implode('/', $meta_dir), $contents) !== false; } return $this->file_save__flat($file_name, implode('/', $meta_dir), $contents) !== false; } /** * Standard Commandr-fs file removal function for Commandr-fs hooks. * * @param array $meta_dir The current meta-directory path * @param string $meta_root_node The root node of the current meta-directory * @param string $file_name The file name * @param object $commandr_fs A reference to the Commandr filesystem object * @return boolean Success? */ public function remove_file($meta_dir, $meta_root_node, $file_name, &$commandr_fs) { if ($file_name == RESOURCE_FS_SPECIAL_DIRECTORY_FILE) { return true; // Fake success, as needs to do so when deleting folder contents } return $this->file_delete($file_name, implode('/', $meta_dir)); } }