diff options
Diffstat (limited to 'engine')
218 files changed, 31494 insertions, 12043 deletions
diff --git a/engine/classes/APIException.php b/engine/classes/APIException.php index 377538c36..b6e1c347b 100644 --- a/engine/classes/APIException.php +++ b/engine/classes/APIException.php @@ -8,4 +8,4 @@ * @package Elgg.Core * @subpackage Exceptions.Stub */ -class APIException extends Exception {}
\ No newline at end of file +class APIException extends Exception {} diff --git a/engine/classes/CallException.php b/engine/classes/CallException.php index 02c580a52..22b8f14f5 100644 --- a/engine/classes/CallException.php +++ b/engine/classes/CallException.php @@ -7,4 +7,4 @@ * @package Elgg.Core * @subpackage Exceptions.Stub */ -class CallException extends Exception {}
\ No newline at end of file +class CallException extends Exception {} diff --git a/engine/classes/ClassNotFoundException.php b/engine/classes/ClassNotFoundException.php index 5170b5333..6a9bcd327 100644 --- a/engine/classes/ClassNotFoundException.php +++ b/engine/classes/ClassNotFoundException.php @@ -7,4 +7,4 @@ * @package Elgg.Core * @subpackage Exceptions */ -class ClassNotFoundException extends ClassException {}
\ No newline at end of file +class ClassNotFoundException extends ClassException {} diff --git a/engine/classes/CronException.php b/engine/classes/CronException.php index 3604c21b9..86370ef31 100644 --- a/engine/classes/CronException.php +++ b/engine/classes/CronException.php @@ -7,4 +7,4 @@ * @package Elgg * @subpackage Exceptions.Stub */ -class CronException extends Exception {}
\ No newline at end of file +class CronException extends Exception {} diff --git a/engine/classes/ElggAccess.php b/engine/classes/ElggAccess.php index 9fc63866a..0aed477fc 100644 --- a/engine/classes/ElggAccess.php +++ b/engine/classes/ElggAccess.php @@ -1,6 +1,6 @@ <?php /** - * Class used to determin if access is being ignored. + * Class used to determine if access is being ignored. * * @package Elgg.Core * @subpackage Access @@ -16,6 +16,7 @@ class ElggAccess { */ private $ignore_access; + // @codingStandardsIgnoreStart /** * Get current ignore access setting. * @@ -26,6 +27,7 @@ class ElggAccess { elgg_deprecated_notice('ElggAccess::get_ignore_access() is deprecated by ElggAccess::getIgnoreAccess()', 1.8); return $this->getIgnoreAccess(); } + // @codingStandardsIgnoreEnd /** * Get current ignore access setting. @@ -36,6 +38,7 @@ class ElggAccess { return $this->ignore_access; } + // @codingStandardsIgnoreStart /** * Set ignore access. * @@ -49,6 +52,7 @@ class ElggAccess { elgg_deprecated_notice('ElggAccess::set_ignore_access() is deprecated by ElggAccess::setIgnoreAccess()', 1.8); return $this->setIgnoreAccess($ignore); } + // @codingStandardsIgnoreEnd /** * Set ignore access. @@ -63,4 +67,4 @@ class ElggAccess { return $prev; } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggAnnotation.php b/engine/classes/ElggAnnotation.php index da43aea23..175e7049d 100644 --- a/engine/classes/ElggAnnotation.php +++ b/engine/classes/ElggAnnotation.php @@ -11,36 +11,46 @@ * @package Elgg.Core * @subpackage DataModel.Annotations * @link http://docs.elgg.org/DataModel/Annotations + * + * @property string $value_type + * @property string $enabled */ class ElggAnnotation extends ElggExtender { + /** + * (non-PHPdoc) + * + * @see ElggData::initializeAttributes() + * + * @return void + */ protected function initializeAttributes() { parent::initializeAttributes(); - + $this->attributes['type'] = 'annotation'; } - + /** - * Construct a new annotation, optionally from a given id value or db object. + * Construct a new annotation object * - * @param mixed $id The annotation ID + * @param mixed $id The annotation ID or a database row as stdClass object */ function __construct($id = null) { $this->initializeAttributes(); if (!empty($id)) { + // Create from db row if ($id instanceof stdClass) { $annotation = $id; - } else { - $annotation = get_annotation($id); - } - if ($annotation) { $objarray = (array) $annotation; - foreach ($objarray as $key => $value) { $this->attributes[$key] = $value; } + } else { + // get an ElggAnnotation object and copy its attributes + $annotation = elgg_get_annotation_from_id($id); + $this->attributes = $annotation->attributes; } } } @@ -49,6 +59,8 @@ class ElggAnnotation extends ElggExtender { * Save this instance * * @return int an object id + * + * @throws IOException */ function save() { if ($this->id > 0) { @@ -59,7 +71,7 @@ class ElggAnnotation extends ElggExtender { $this->value_type, $this->owner_guid, $this->access_id); if (!$this->id) { - throw new IOException(sprintf(elgg_echo('IOException:UnableToSaveNew'), get_class())); + throw new IOException(elgg_echo('IOException:UnableToSaveNew', array(get_class()))); } return $this->id; } @@ -71,7 +83,28 @@ class ElggAnnotation extends ElggExtender { * @return bool */ function delete() { - return delete_annotation($this->id); + elgg_delete_river(array('annotation_id' => $this->id)); + return elgg_delete_metastring_based_object_by_id($this->id, 'annotations'); + } + + /** + * Disable the annotation. + * + * @return bool + * @since 1.8 + */ + function disable() { + return elgg_set_metastring_based_object_enabled_by_id($this->id, 'no', 'annotations'); + } + + /** + * Enable the annotation. + * + * @return bool + * @since 1.8 + */ + function enable() { + return elgg_set_metastring_based_object_enabled_by_id($this->id, 'yes', 'annotations'); } /** @@ -95,6 +128,6 @@ class ElggAnnotation extends ElggExtender { * @return ElggAnnotation */ public function getObjectFromID($id) { - return get_annotation($id); + return elgg_get_annotation_from_id($id); } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggAttributeLoader.php b/engine/classes/ElggAttributeLoader.php new file mode 100644 index 000000000..ffc80b02d --- /dev/null +++ b/engine/classes/ElggAttributeLoader.php @@ -0,0 +1,248 @@ +<?php + +/** + * Loads ElggEntity attributes from DB or validates those passed in via constructor + * + * @access private + * + * @package Elgg.Core + * @subpackage DataModel + */ +class ElggAttributeLoader { + + /** + * @var array names of attributes in all entities + */ + protected static $primary_attr_names = array( + 'guid', + 'type', + 'subtype', + 'owner_guid', + 'container_guid', + 'site_guid', + 'access_id', + 'time_created', + 'time_updated', + 'last_action', + 'enabled', + ); + + /** + * @var array names of secondary attributes required for the entity + */ + protected $secondary_attr_names = array(); + + /** + * @var string entity type (not class) required for fetched primaries + */ + protected $required_type; + + /** + * @var array + */ + protected $initialized_attributes; + + /** + * @var string class of object being loaded + */ + protected $class; + + /** + * @var bool should access control be considered when fetching entity? + */ + public $requires_access_control = true; + + /** + * @var callable function used to load attributes from {prefix}entities table + */ + public $primary_loader = 'get_entity_as_row'; + + /** + * @var callable function used to load attributes from secondary table + */ + public $secondary_loader = ''; + + /** + * @var callable function used to load all necessary attributes + */ + public $full_loader = ''; + + /** + * Constructor + * + * @param string $class class of object being loaded + * @param string $required_type entity type this is being used to populate + * @param array $initialized_attrs attributes after initializeAttributes() has been run + * @throws InvalidArgumentException + */ + public function __construct($class, $required_type, array $initialized_attrs) { + if (!is_string($class)) { + throw new InvalidArgumentException('$class must be a class name.'); + } + $this->class = $class; + + if (!is_string($required_type)) { + throw new InvalidArgumentException('$requiredType must be a system entity type.'); + } + $this->required_type = $required_type; + + $this->initialized_attributes = $initialized_attrs; + unset($initialized_attrs['tables_split'], $initialized_attrs['tables_loaded']); + $all_attr_names = array_keys($initialized_attrs); + $this->secondary_attr_names = array_diff($all_attr_names, self::$primary_attr_names); + } + + /** + * Get primary attributes missing that are missing + * + * @param stdClass $row Database row + * @return array + */ + protected function isMissingPrimaries($row) { + return array_diff(self::$primary_attr_names, array_keys($row)) !== array(); + } + + /** + * Get secondary attributes that are missing + * + * @param stdClass $row Database row + * @return array + */ + protected function isMissingSecondaries($row) { + return array_diff($this->secondary_attr_names, array_keys($row)) !== array(); + } + + /** + * Check that the type is correct + * + * @param stdClass $row Database row + * @return void + * @throws InvalidClassException + */ + protected function checkType($row) { + if ($row['type'] !== $this->required_type) { + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($row['guid'], $this->class)); + throw new InvalidClassException($msg); + } + } + + /** + * Get all required attributes for the entity, validating any that are passed in. Returns empty array + * if can't be loaded (Check $failure_reason). + * + * This function splits loading between "primary" attributes (those in {prefix}entities table) and + * "secondary" attributes (e.g. those in {prefix}objects_entity), but can load all at once if a + * combined loader is available. + * + * @param mixed $row a row loaded from DB (array or stdClass) or a GUID + * @return array will be empty if failed to load all attributes (access control or entity doesn't exist) + * + * @throws InvalidArgumentException|LogicException|IncompleteEntityException + */ + public function getRequiredAttributes($row) { + if (!is_array($row) && !($row instanceof stdClass)) { + // assume row is the GUID + $row = array('guid' => $row); + } + $row = (array) $row; + if (empty($row['guid'])) { + throw new InvalidArgumentException('$row must be or contain a GUID'); + } + + // these must be present to support isFullyLoaded() + foreach (array('tables_split', 'tables_loaded') as $key) { + if (isset($this->initialized_attributes[$key])) { + $row[$key] = $this->initialized_attributes[$key]; + } + } + + $was_missing_primaries = $this->isMissingPrimaries($row); + $was_missing_secondaries = $this->isMissingSecondaries($row); + + // some types have a function to load all attributes at once, it should be faster + if (($was_missing_primaries || $was_missing_secondaries) && is_callable($this->full_loader)) { + $fetched = (array) call_user_func($this->full_loader, $row['guid']); + if (!$fetched) { + return array(); + } + $row = array_merge($row, $fetched); + $this->checkType($row); + } else { + if ($was_missing_primaries) { + if (!is_callable($this->primary_loader)) { + throw new LogicException('Primary attribute loader must be callable'); + } + if ($this->requires_access_control) { + $fetched = (array) call_user_func($this->primary_loader, $row['guid']); + } else { + $ignoring_access = elgg_set_ignore_access(); + $fetched = (array) call_user_func($this->primary_loader, $row['guid']); + elgg_set_ignore_access($ignoring_access); + } + if (!$fetched) { + return array(); + } + $row = array_merge($row, $fetched); + } + + // We must test type before trying to load the secondaries so that InvalidClassException + // gets thrown. Otherwise the secondary loader will fail and return false. + $this->checkType($row); + + if ($was_missing_secondaries) { + if (!is_callable($this->secondary_loader)) { + throw new LogicException('Secondary attribute loader must be callable'); + } + $fetched = (array) call_user_func($this->secondary_loader, $row['guid']); + if (!$fetched) { + if ($row['type'] === 'site') { + // A special case is needed for sites: When vanilla ElggEntities are created and + // saved, these are stored w/ type "site", but with no sites_entity row. These + // are probably only created in the unit tests. + // @todo Don't save vanilla ElggEntities with type "site" + + $row = $this->filterAddedColumns($row); + $row['guid'] = (int) $row['guid']; + return $row; + } + throw new IncompleteEntityException("Secondary loader failed to return row for {$row['guid']}"); + } + $row = array_merge($row, $fetched); + } + } + + $row = $this->filterAddedColumns($row); + + // Note: If there are still missing attributes, we're running on a 1.7 or earlier schema. We let + // this pass so the upgrades can run. + + // guid needs to be an int https://github.com/elgg/elgg/issues/4111 + $row['guid'] = (int) $row['guid']; + + return $row; + } + + /** + * Filter out keys returned by the query which should not appear in the entity's attributes + * + * @param array $row All columns from the query + * @return array Columns acceptable for the entity's attributes + */ + protected function filterAddedColumns($row) { + // make an array with keys as acceptable attribute names + $acceptable_attrs = self::$primary_attr_names; + array_splice($acceptable_attrs, count($acceptable_attrs), 0, $this->secondary_attr_names); + $acceptable_attrs = array_combine($acceptable_attrs, $acceptable_attrs); + + // @todo remove these when #4584 is in place + $acceptable_attrs['tables_split'] = true; + $acceptable_attrs['tables_loaded'] = true; + + foreach ($row as $key => $val) { + if (!isset($acceptable_attrs[$key])) { + unset($row[$key]); + } + } + return $row; + } +} diff --git a/engine/classes/ElggAutoP.php b/engine/classes/ElggAutoP.php new file mode 100644 index 000000000..05842d1b2 --- /dev/null +++ b/engine/classes/ElggAutoP.php @@ -0,0 +1,336 @@ +<?php + +/** + * Create wrapper P and BR elements in HTML depending on newlines. Useful when + * users use newlines to signal line and paragraph breaks. In all cases output + * should be well-formed markup. + * + * In DIV elements, Ps are only added when there would be at + * least two of them. + * + * @package Elgg.Core + * @subpackage Output + */ +class ElggAutoP { + + public $encoding = 'UTF-8'; + + /** + * @var DOMDocument + */ + protected $_doc = null; + + /** + * @var DOMXPath + */ + protected $_xpath = null; + + protected $_blocks = 'address article area aside blockquote caption col colgroup dd + details div dl dt fieldset figure figcaption footer form h1 h2 h3 h4 h5 h6 header + hr hgroup legend map math menu nav noscript p pre section select style summary + table tbody td tfoot th thead tr ul ol option li'; + + /** + * @var array + */ + protected $_inlines = 'a abbr audio b button canvas caption cite code command datalist + del dfn em embed i iframe img input ins kbd keygen label map mark meter object + output progress q rp rt ruby s samp script select small source span strong style + sub sup textarea time var video wbr'; + + /** + * Descend into these elements to add Ps + * + * @var array + */ + protected $_descendList = 'article aside blockquote body details div footer form + header section'; + + /** + * Add Ps inside these elements + * + * @var array + */ + protected $_alterList = 'article aside blockquote body details div footer header + section'; + + /** @var string */ + protected $_unique = ''; + + /** + * Constructor + */ + public function __construct() { + $this->_blocks = preg_split('@\\s+@', $this->_blocks); + $this->_descendList = preg_split('@\\s+@', $this->_descendList); + $this->_alterList = preg_split('@\\s+@', $this->_alterList); + $this->_inlines = preg_split('@\\s+@', $this->_inlines); + $this->_unique = md5(__FILE__); + } + + /** + * Intance of class for singleton pattern. + * @var ElggAutoP + */ + private static $instance; + + /** + * Singleton pattern. + * @return ElggAutoP + */ + public static function getInstance() { + $className = __CLASS__; + if (!(self::$instance instanceof $className)) { + self::$instance = new $className(); + } + return self::$instance; + } + + /** + * Create wrapper P and BR elements in HTML depending on newlines. Useful when + * users use newlines to signal line and paragraph breaks. In all cases output + * should be well-formed markup. + * + * In DIV, LI, TD, and TH elements, Ps are only added when their would be at + * least two of them. + * + * @param string $html snippet + * @return string|false output or false if parse error occurred + */ + public function process($html) { + // normalize whitespace + $html = str_replace(array("\r\n", "\r"), "\n", $html); + + // allows preserving entities untouched + $html = str_replace('&', $this->_unique . 'AMP', $html); + + $this->_doc = new DOMDocument(); + + // parse to DOM, suppressing loadHTML warnings + // http://www.php.net/manual/en/domdocument.loadhtml.php#95463 + libxml_use_internal_errors(true); + + // Do not load entities. May be unnecessary, better safe than sorry + $disable_load_entities = libxml_disable_entity_loader(true); + + if (!$this->_doc->loadHTML("<html><meta http-equiv='content-type' " + . "content='text/html; charset={$this->encoding}'><body>{$html}</body>" + . "</html>")) { + + libxml_disable_entity_loader($disable_load_entities); + return false; + } + + libxml_disable_entity_loader($disable_load_entities); + + $this->_xpath = new DOMXPath($this->_doc); + // start processing recursively at the BODY element + $nodeList = $this->_xpath->query('//body[1]'); + $this->addParagraphs($nodeList->item(0)); + + // serialize back to HTML + $html = $this->_doc->saveHTML(); + + // Note: we create <autop> elements, which will later be converted to paragraphs + + // split AUTOPs into multiples at /\n\n+/ + $html = preg_replace('/(' . $this->_unique . 'NL){2,}/', '</autop><autop>', $html); + $html = str_replace(array($this->_unique . 'BR', $this->_unique . 'NL', '<br>'), + '<br />', + $html); + $html = str_replace('<br /></autop>', '</autop>', $html); + + // re-parse so we can handle new AUTOP elements + + // Do not load entities. May be unnecessary, better safe than sorry + $disable_load_entities = libxml_disable_entity_loader(true); + + if (!$this->_doc->loadHTML($html)) { + libxml_disable_entity_loader($disable_load_entities); + return false; + } + + libxml_disable_entity_loader($disable_load_entities); + + // must re-create XPath object after DOM load + $this->_xpath = new DOMXPath($this->_doc); + + // strip AUTOPs that only have comments/whitespace + foreach ($this->_xpath->query('//autop') as $autop) { + /* @var DOMElement $autop */ + $hasContent = false; + if (trim($autop->textContent) !== '') { + $hasContent = true; + } else { + foreach ($autop->childNodes as $node) { + if ($node->nodeType === XML_ELEMENT_NODE) { + $hasContent = true; + break; + } + } + } + if (!$hasContent) { + // mark to be later replaced w/ preg_replace (faster than moving nodes out) + $autop->setAttribute("r", "1"); + } + } + + // If a DIV contains a single AUTOP, remove it + foreach ($this->_xpath->query('//div') as $el) { + /* @var DOMElement $el */ + $autops = $this->_xpath->query('./autop', $el); + if ($autops->length === 1) { + $firstAutop = $autops->item(0); + /* @var DOMElement $firstAutop */ + $firstAutop->setAttribute("r", "1"); + } + } + + $html = $this->_doc->saveHTML(); + + // trim to the contents of BODY + $bodyStart = strpos($html, '<body>'); + $bodyEnd = strpos($html, '</body>', $bodyStart + 6); + $html = substr($html, $bodyStart + 6, $bodyEnd - $bodyStart - 6); + + // strip AUTOPs that should be removed + $html = preg_replace('@<autop r="1">(.*?)</autop>@', '\\1', $html); + + // commit to converting AUTOPs to Ps + $html = str_replace('<autop>', "\n<p>", $html); + $html = str_replace('</autop>', "</p>\n", $html); + + $html = str_replace('<br>', '<br />', $html); + $html = str_replace($this->_unique . 'AMP', '&', $html); + return $html; + } + + /** + * Add P and BR elements as necessary + * + * @param DOMElement $el DOM element + * @return void + */ + protected function addParagraphs(DOMElement $el) { + // no need to call recursively, just queue up + $elsToProcess = array($el); + $inlinesToProcess = array(); + while ($el = array_shift($elsToProcess)) { + // if true, we can alter all child nodes, if not, we'll just call + // addParagraphs on each element in the descendInto list + $alterInline = in_array($el->nodeName, $this->_alterList); + + // inside affected elements, we want to trim leading whitespace from + // the first text node + $ltrimFirstTextNode = true; + + // should we open a new AUTOP element to move inline elements into? + $openP = true; + $autop = null; + + // after BR, ignore a newline + $isFollowingBr = false; + + $node = $el->firstChild; + while (null !== $node) { + if ($alterInline) { + if ($openP) { + $openP = false; + // create a P to move inline content into (this may be removed later) + $autop = $el->insertBefore($this->_doc->createElement('autop'), $node); + } + } + + $isElement = ($node->nodeType === XML_ELEMENT_NODE); + if ($isElement) { + $isBlock = in_array($node->nodeName, $this->_blocks); + } else { + $isBlock = false; + } + + if ($alterInline) { + $isText = ($node->nodeType === XML_TEXT_NODE); + $isLastInline = (! $node->nextSibling + || ($node->nextSibling->nodeType === XML_ELEMENT_NODE + && in_array($node->nextSibling->nodeName, $this->_blocks))); + if ($isElement) { + $isFollowingBr = ($node->nodeName === 'br'); + } + + if ($isText) { + $nodeText = $node->nodeValue; + if ($ltrimFirstTextNode) { + $nodeText = ltrim($nodeText); + $ltrimFirstTextNode = false; + } + if ($isFollowingBr && preg_match('@^[ \\t]*\\n[ \\t]*@', $nodeText, $m)) { + // if a user ends a line with <br>, don't add a second BR + $nodeText = substr($nodeText, strlen($m[0])); + } + if ($isLastInline) { + $nodeText = rtrim($nodeText); + } + $nodeText = str_replace("\n", $this->_unique . 'NL', $nodeText); + $tmpNode = $node; + $node = $node->nextSibling; // move loop to next node + + // alter node in place, then move into AUTOP + $tmpNode->nodeValue = $nodeText; + $autop->appendChild($tmpNode); + + continue; + } + } + if ($isBlock || ! $node->nextSibling) { + if ($isBlock) { + if (in_array($node->nodeName, $this->_descendList)) { + $elsToProcess[] = $node; + //$this->addParagraphs($node); + } + } + $openP = true; + $ltrimFirstTextNode = true; + } + if ($alterInline) { + if (! $isBlock) { + $tmpNode = $node; + if ($isElement && false !== strpos($tmpNode->textContent, "\n")) { + $inlinesToProcess[] = $tmpNode; + } + $node = $node->nextSibling; + $autop->appendChild($tmpNode); + continue; + } + } + + $node = $node->nextSibling; + } + } + + // handle inline nodes + // no need to recurse, just queue up + while ($el = array_shift($inlinesToProcess)) { + $ignoreLeadingNewline = false; + foreach ($el->childNodes as $node) { + if ($node->nodeType === XML_ELEMENT_NODE) { + if ($node->nodeValue === 'BR') { + $ignoreLeadingNewline = true; + } else { + $ignoreLeadingNewline = false; + if (false !== strpos($node->textContent, "\n")) { + $inlinesToProcess[] = $node; + } + } + continue; + } elseif ($node->nodeType === XML_TEXT_NODE) { + $text = $node->nodeValue; + if ($text[0] === "\n" && $ignoreLeadingNewline) { + $text = substr($text, 1); + $ignoreLeadingNewline = false; + } + $node->nodeValue = str_replace("\n", $this->_unique . 'BR', $text); + } + } + } + } +} diff --git a/engine/classes/ElggBatch.php b/engine/classes/ElggBatch.php new file mode 100644 index 000000000..d810ea066 --- /dev/null +++ b/engine/classes/ElggBatch.php @@ -0,0 +1,433 @@ +<?php +/** + * Efficiently run operations on batches of results for any function + * that supports an options array. + * + * This is usually used with elgg_get_entities() and friends, + * elgg_get_annotations(), and elgg_get_metadata(). + * + * If you pass a valid PHP callback, all results will be run through that + * callback. You can still foreach() through the result set after. Valid + * PHP callbacks can be a string, an array, or a closure. + * {@link http://php.net/manual/en/language.pseudo-types.php} + * + * The callback function must accept 3 arguments: an entity, the getter + * used, and the options used. + * + * Results from the callback are stored in callbackResult. If the callback + * returns only booleans, callbackResults will be the combined result of + * all calls. If no entities are processed, callbackResults will be null. + * + * If the callback returns anything else, callbackresult will be an indexed + * array of whatever the callback returns. If returning error handling + * information, you should include enough information to determine which + * result you're referring to. + * + * Don't combine returning bools and returning something else. + * + * Note that returning false will not stop the foreach. + * + * @warning If your callback or foreach loop deletes or disable entities + * you MUST call setIncrementOffset(false) or set that when instantiating. + * This forces the offset to stay what it was in the $options array. + * + * @example + * <code> + * // using foreach + * $batch = new ElggBatch('elgg_get_entities', array()); + * $batch->setIncrementOffset(false); + * + * foreach ($batch as $entity) { + * $entity->disable(); + * } + * + * // using both a callback + * $callback = function($result, $getter, $options) { + * var_dump("Looking at annotation id: $result->id"); + * return true; + * } + * + * $batch = new ElggBatch('elgg_get_annotations', array('guid' => 2), $callback); + * </code> + * + * @package Elgg.Core + * @subpackage DataModel + * @link http://docs.elgg.org/DataModel/ElggBatch + * @since 1.8 + */ +class ElggBatch + implements Iterator { + + /** + * The objects to interator over. + * + * @var array + */ + private $results = array(); + + /** + * The function used to get results. + * + * @var mixed A string, array, or closure, or lamda function + */ + private $getter = null; + + /** + * The number of results to grab at a time. + * + * @var int + */ + private $chunkSize = 25; + + /** + * A callback function to pass results through. + * + * @var mixed A string, array, or closure, or lamda function + */ + private $callback = null; + + /** + * Start after this many results. + * + * @var int + */ + private $offset = 0; + + /** + * Stop after this many results. + * + * @var int + */ + private $limit = 0; + + /** + * Number of processed results. + * + * @var int + */ + private $retrievedResults = 0; + + /** + * The index of the current result within the current chunk + * + * @var int + */ + private $resultIndex = 0; + + /** + * The index of the current chunk + * + * @var int + */ + private $chunkIndex = 0; + + /** + * The number of results iterated through + * + * @var int + */ + private $processedResults = 0; + + /** + * Is the getter a valid callback + * + * @var bool + */ + private $validGetter = null; + + /** + * The result of running all entities through the callback function. + * + * @var mixed + */ + public $callbackResult = null; + + /** + * If false, offset will not be incremented. This is used for callbacks/loops that delete. + * + * @var bool + */ + private $incrementOffset = true; + + /** + * Entities that could not be instantiated during a fetch + * + * @var stdClass[] + */ + private $incompleteEntities = array(); + + /** + * Total number of incomplete entities fetched + * + * @var int + */ + private $totalIncompletes = 0; + + /** + * Batches operations on any elgg_get_*() or compatible function that supports + * an options array. + * + * Instead of returning all objects in memory, it goes through $chunk_size + * objects, then requests more from the server. This avoids OOM errors. + * + * @param string $getter The function used to get objects. Usually + * an elgg_get_*() function, but can be any valid PHP callback. + * @param array $options The options array to pass to the getter function. If limit is + * not set, 10 is used as the default. In most cases that is not + * what you want. + * @param mixed $callback An optional callback function that all results will be passed + * to upon load. The callback needs to accept $result, $getter, + * $options. + * @param int $chunk_size The number of entities to pull in before requesting more. + * You have to balance this between running out of memory in PHP + * and hitting the db server too often. + * @param bool $inc_offset Increment the offset on each fetch. This must be false for + * callbacks that delete rows. You can set this after the + * object is created with {@see ElggBatch::setIncrementOffset()}. + */ + public function __construct($getter, $options, $callback = null, $chunk_size = 25, + $inc_offset = true) { + + $this->getter = $getter; + $this->options = $options; + $this->callback = $callback; + $this->chunkSize = $chunk_size; + $this->setIncrementOffset($inc_offset); + + if ($this->chunkSize <= 0) { + $this->chunkSize = 25; + } + + // store these so we can compare later + $this->offset = elgg_extract('offset', $options, 0); + $this->limit = elgg_extract('limit', $options, 10); + + // if passed a callback, create a new ElggBatch with the same options + // and pass each to the callback. + if ($callback && is_callable($callback)) { + $batch = new ElggBatch($getter, $options, null, $chunk_size, $inc_offset); + + $all_results = null; + + foreach ($batch as $result) { + if (is_string($callback)) { + $result = $callback($result, $getter, $options); + } else { + $result = call_user_func_array($callback, array($result, $getter, $options)); + } + + if (!isset($all_results)) { + if ($result === true || $result === false || $result === null) { + $all_results = $result; + } else { + $all_results = array(); + } + } + + if (($result === true || $result === false || $result === null) && !is_array($all_results)) { + $all_results = $result && $all_results; + } else { + $all_results[] = $result; + } + } + + $this->callbackResult = $all_results; + } + } + + /** + * Tell the process that an entity was incomplete during a fetch + * + * @param stdClass $row + * + * @access private + */ + public function reportIncompleteEntity(stdClass $row) { + $this->incompleteEntities[] = $row; + } + + /** + * Fetches the next chunk of results + * + * @return bool + */ + private function getNextResultsChunk() { + + // always reset results. + $this->results = array(); + + if (!isset($this->validGetter)) { + $this->validGetter = is_callable($this->getter); + } + + if (!$this->validGetter) { + return false; + } + + $limit = $this->chunkSize; + + // if someone passed limit = 0 they want everything. + if ($this->limit != 0) { + if ($this->retrievedResults >= $this->limit) { + return false; + } + + // if original limit < chunk size, set limit to original limit + // else if the number of results we'll fetch if greater than the original limit + if ($this->limit < $this->chunkSize) { + $limit = $this->limit; + } elseif ($this->retrievedResults + $this->chunkSize > $this->limit) { + // set the limit to the number of results remaining in the original limit + $limit = $this->limit - $this->retrievedResults; + } + } + + if ($this->incrementOffset) { + $offset = $this->offset + $this->retrievedResults; + } else { + $offset = $this->offset + $this->totalIncompletes; + } + + $current_options = array( + 'limit' => $limit, + 'offset' => $offset, + '__ElggBatch' => $this, + ); + + $options = array_merge($this->options, $current_options); + + $this->incompleteEntities = array(); + $this->results = call_user_func_array($this->getter, array($options)); + + $num_results = count($this->results); + $num_incomplete = count($this->incompleteEntities); + + $this->totalIncompletes += $num_incomplete; + + if ($this->incompleteEntities) { + // pad the front of the results with nulls representing the incompletes + array_splice($this->results, 0, 0, array_pad(array(), $num_incomplete, null)); + // ...and skip past them + reset($this->results); + for ($i = 0; $i < $num_incomplete; $i++) { + next($this->results); + } + } + + if ($this->results) { + $this->chunkIndex++; + + // let the system know we've jumped past the nulls + $this->resultIndex = $num_incomplete; + + $this->retrievedResults += ($num_results + $num_incomplete); + if ($num_results == 0) { + // This fetch was *all* incompletes! We need to fetch until we can either + // offer at least one row to iterate over, or give up. + return $this->getNextResultsChunk(); + } + return true; + } else { + return false; + } + } + + /** + * Increment the offset from the original options array? Setting to + * false is required for callbacks that delete rows. + * + * @param bool $increment Set to false when deleting data + * @return void + */ + public function setIncrementOffset($increment = true) { + $this->incrementOffset = (bool) $increment; + } + + /** + * Implements Iterator + */ + + /** + * PHP Iterator Interface + * + * @see Iterator::rewind() + * @return void + */ + public function rewind() { + $this->resultIndex = 0; + $this->retrievedResults = 0; + $this->processedResults = 0; + + // only grab results if we haven't yet or we're crossing chunks + if ($this->chunkIndex == 0 || $this->limit > $this->chunkSize) { + $this->chunkIndex = 0; + $this->getNextResultsChunk(); + } + } + + /** + * PHP Iterator Interface + * + * @see Iterator::current() + * @return mixed + */ + public function current() { + return current($this->results); + } + + /** + * PHP Iterator Interface + * + * @see Iterator::key() + * @return int + */ + public function key() { + return $this->processedResults; + } + + /** + * PHP Iterator Interface + * + * @see Iterator::next() + * @return mixed + */ + public function next() { + // if we'll be at the end. + if (($this->processedResults + 1) >= $this->limit && $this->limit > 0) { + $this->results = array(); + return false; + } + + // if we'll need new results. + if (($this->resultIndex + 1) >= $this->chunkSize) { + if (!$this->getNextResultsChunk()) { + $this->results = array(); + return false; + } + + $result = current($this->results); + } else { + // the function above resets the indexes, so only inc if not + // getting new set + $this->resultIndex++; + $result = next($this->results); + } + + $this->processedResults++; + return $result; + } + + /** + * PHP Iterator Interface + * + * @see Iterator::valid() + * @return bool + */ + public function valid() { + if (!is_array($this->results)) { + return false; + } + $key = key($this->results); + return ($key !== NULL && $key !== FALSE); + } +} diff --git a/engine/classes/ElggCache.php b/engine/classes/ElggCache.php index 549772d6d..909eab39b 100644 --- a/engine/classes/ElggCache.php +++ b/engine/classes/ElggCache.php @@ -6,9 +6,7 @@ * @package Elgg.Core * @subpackage Cache */ -abstract class ElggCache implements - // Override for array access - ArrayAccess { +abstract class ElggCache implements ArrayAccess { /** * Variables for the cache object. * @@ -23,6 +21,7 @@ abstract class ElggCache implements $this->variables = array(); } + // @codingStandardsIgnoreStart /** * Set a cache variable. * @@ -31,12 +30,13 @@ abstract class ElggCache implements * * @return void * - * @deprecated 1.8 Use ElggAccess:setVariable() + * @deprecated 1.8 Use ElggCache:setVariable() */ public function set_variable($variable, $value) { elgg_deprecated_notice('ElggCache::set_variable() is deprecated by ElggCache::setVariable()', 1.8); $this->setVariable($variable, $value); } + // @codingStandardsIgnoreEnd /** * Set a cache variable. @@ -54,6 +54,7 @@ abstract class ElggCache implements $this->variables[$variable] = $value; } + // @codingStandardsIgnoreStart /** * Get variables for this cache. * @@ -67,6 +68,7 @@ abstract class ElggCache implements elgg_deprecated_notice('ElggCache::get_variable() is deprecated by ElggCache::getVariable()', 1.8); return $this->getVariable($variable); } + // @codingStandardsIgnoreEnd /** * Get variables for this cache. @@ -141,6 +143,9 @@ abstract class ElggCache implements /** * Load data from the cache using a given key. * + * @todo $offset is a horrible variable name because it creates confusion + * with the ArrayAccess methods + * * @param string $key Name * @param int $offset Offset * @param int $limit Limit @@ -186,12 +191,12 @@ abstract class ElggCache implements // ARRAY ACCESS INTERFACE ////////////////////////////////////////////////////////// /** - * Set offset + * Assigns a value for the specified key * * @see ArrayAccess::offsetSet() * - * @param mixed $key Name - * @param mixed $value Value + * @param mixed $key The key (offset) to assign the value to. + * @param mixed $value The value to set. * * @return void */ @@ -200,43 +205,43 @@ abstract class ElggCache implements } /** - * Get offset + * Get the value for specified key * * @see ArrayAccess::offsetGet() * - * @param mixed $key Name + * @param mixed $key The key (offset) to retrieve. * - * @return void + * @return mixed */ function offsetGet($key) { return $this->load($key); } /** - * Unsets offset + * Unsets a key. * * @see ArrayAccess::offsetUnset() * - * @param mixed $key Name + * @param mixed $key The key (offset) to unset. * * @return void */ function offsetUnset($key) { - if (isset($this->key)) { - unset($this->key); + if (isset($this->$key)) { + unset($this->$key); } } /** - * Does offset exist + * Does key exist * * @see ArrayAccess::offsetExists() * - * @param mixed $offset Offset + * @param mixed $key A key (offset) to check for. * - * @return void + * @return bool */ - function offsetExists($offset) { - return isset($this->$offset); + function offsetExists($key) { + return isset($this->$key); } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggCrypto.php b/engine/classes/ElggCrypto.php new file mode 100644 index 000000000..317d371e4 --- /dev/null +++ b/engine/classes/ElggCrypto.php @@ -0,0 +1,208 @@ +<?php +/** + * ElggCrypto + * + * @package Elgg.Core + * @subpackage Crypto + * + * @access private + */ +class ElggCrypto { + + /** + * Character set for temp passwords (no risk of embedded profanity/glyphs that look similar) + */ + const CHARS_PASSWORD = 'bcdfghjklmnpqrstvwxyz2346789'; + + /** + * Generate a string of highly randomized bytes (over the full 8-bit range). + * + * @param int $length Number of bytes needed + * @return string Random bytes + * + * @author George Argyros <argyros.george@gmail.com> + * @copyright 2012, George Argyros. All rights reserved. + * @license Modified BSD + * @link https://github.com/GeorgeArgyros/Secure-random-bytes-in-PHP/blob/master/srand.php Original + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the <organization> nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GEORGE ARGYROS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + public function getRandomBytes($length) { + /** + * Our primary choice for a cryptographic strong randomness function is + * openssl_random_pseudo_bytes. + */ + $SSLstr = '4'; // http://xkcd.com/221/ + if (function_exists('openssl_random_pseudo_bytes') + && (version_compare(PHP_VERSION, '5.3.4') >= 0 || substr(PHP_OS, 0, 3) !== 'WIN')) { + $SSLstr = openssl_random_pseudo_bytes($length, $strong); + if ($strong) { + return $SSLstr; + } + } + + /** + * If mcrypt extension is available then we use it to gather entropy from + * the operating system's PRNG. This is better than reading /dev/urandom + * directly since it avoids reading larger blocks of data than needed. + * Older versions of mcrypt_create_iv may be broken or take too much time + * to finish so we only use this function with PHP 5.3.7 and above. + * @see https://bugs.php.net/bug.php?id=55169 + */ + if (function_exists('mcrypt_create_iv') + && (version_compare(PHP_VERSION, '5.3.7') >= 0 || substr(PHP_OS, 0, 3) !== 'WIN')) { + $str = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + if ($str !== false) { + return $str; + } + } + + /** + * No build-in crypto randomness function found. We collect any entropy + * available in the PHP core PRNGs along with some filesystem info and memory + * stats. To make this data cryptographically strong we add data either from + * /dev/urandom or if its unavailable, we gather entropy by measuring the + * time needed to compute a number of SHA-1 hashes. + */ + $str = ''; + $bits_per_round = 2; // bits of entropy collected in each clock drift round + $msec_per_round = 400; // expected running time of each round in microseconds + $hash_len = 20; // SHA-1 Hash length + $total = $length; // total bytes of entropy to collect + + $handle = @fopen('/dev/urandom', 'rb'); + if ($handle && function_exists('stream_set_read_buffer')) { + @stream_set_read_buffer($handle, 0); + } + + do { + $bytes = ($total > $hash_len) ? $hash_len : $total; + $total -= $bytes; + + //collect any entropy available from the PHP system and filesystem + $entropy = rand() . uniqid(mt_rand(), true) . $SSLstr; + $entropy .= implode('', @fstat(@fopen(__FILE__, 'r'))); + $entropy .= memory_get_usage() . getmypid(); + $entropy .= serialize($_ENV) . serialize($_SERVER); + if (function_exists('posix_times')) { + $entropy .= serialize(posix_times()); + } + if (function_exists('zend_thread_id')) { + $entropy .= zend_thread_id(); + } + + if ($handle) { + $entropy .= @fread($handle, $bytes); + } else { + // Measure the time that the operations will take on average + for ($i = 0; $i < 3; $i++) { + $c1 = microtime(true); + $var = sha1(mt_rand()); + for ($j = 0; $j < 50; $j++) { + $var = sha1($var); + } + $c2 = microtime(true); + $entropy .= $c1 . $c2; + } + + // Based on the above measurement determine the total rounds + // in order to bound the total running time. + $rounds = (int) ($msec_per_round * 50 / (int) (($c2 - $c1) * 1000000)); + + // Take the additional measurements. On average we can expect + // at least $bits_per_round bits of entropy from each measurement. + $iter = $bytes * (int) (ceil(8 / $bits_per_round)); + + for ($i = 0; $i < $iter; $i++) { + $c1 = microtime(); + $var = sha1(mt_rand()); + for ($j = 0; $j < $rounds; $j++) { + $var = sha1($var); + } + $c2 = microtime(); + $entropy .= $c1 . $c2; + } + } + + // We assume sha1 is a deterministic extractor for the $entropy variable. + $str .= sha1($entropy, true); + + } while ($length > strlen($str)); + + if ($handle) { + @fclose($handle); + } + + return substr($str, 0, $length); + } + + /** + * Generate a random string of specified length. + * + * Uses supplied character list for generating the new string. + * If no character list provided - uses Base64 URL character set. + * + * @param int $length Desired length of the string + * @param string|null $chars Characters to be chosen from randomly. If not given, the Base64 URL + * charset will be used. + * + * @return string The random string + * + * @throws InvalidArgumentException + * + * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * + * @see https://github.com/zendframework/zf2/blob/master/library/Zend/Math/Rand.php#L179 + */ + public static function getRandomString($length, $chars = null) { + if ($length < 1) { + throw new InvalidArgumentException('Length should be >= 1'); + } + + if (empty($chars)) { + $numBytes = ceil($length * 0.75); + $bytes = self::getRandomBytes($numBytes); + $string = substr(rtrim(base64_encode($bytes), '='), 0, $length); + + // Base64 URL + return strtr($string, '+/', '-_'); + } + + $listLen = strlen($chars); + + if ($listLen == 1) { + return str_repeat($chars, $length); + } + + $bytes = self::getRandomBytes($length); + $pos = 0; + $result = ''; + for ($i = 0; $i < $length; $i++) { + $pos = ($pos + ord($bytes[$i])) % $listLen; + $result .= $chars[$pos]; + } + + return $result; + } +} diff --git a/engine/classes/ElggData.php b/engine/classes/ElggData.php index 4591c499d..4f843cde4 100644 --- a/engine/classes/ElggData.php +++ b/engine/classes/ElggData.php @@ -1,11 +1,21 @@ <?php +/** + * A generic class that contains shared code b/w + * ElggExtender, ElggEntity, and ElggRelationship + * + * @package Elgg.Core + * @subpackage DataModel + * + * @property int $owner_guid + * @property int $time_created + */ abstract class ElggData implements Loggable, // Can events related to this object class be logged Iterator, // Override foreach behaviour ArrayAccess, // Override for array access Exportable { - + /** * The main attributes of an entity. * Holds attributes to save to database @@ -15,7 +25,28 @@ abstract class ElggData implements * Any field not appearing in this will be viewed as a */ protected $attributes = array(); - + + // @codingStandardsIgnoreStart + /** + * Initialise the attributes array. + * + * This is vital to distinguish between metadata and base parameters. + * + * @param bool $pre18_api Compatibility for subclassing in 1.7 -> 1.8 change. + * Passing true (default) emits a deprecation notice. + * Passing false returns false. Core constructors always pass false. + * Does nothing either way since attributes are initialized by the time + * this is called. + * @return void + * @deprecated 1.8 Use initializeAttributes() + */ + protected function initialise_attributes($pre18_api = true) { + if ($pre18_api) { + elgg_deprecated_notice('initialise_attributes() is deprecated by initializeAttributes()', 1.8); + } + } + // @codingStandardsIgnoreEnd + /** * Initialize the attributes array. * @@ -28,8 +59,8 @@ abstract class ElggData implements if (!is_array($this->attributes)) { $this->attributes = array(); } - - $this->attributes['time_created'] = ''; + + $this->attributes['time_created'] = NULL; } /** @@ -54,7 +85,7 @@ abstract class ElggData implements public function __set($name, $value) { return $this->set($name, $value); } - + /** * Test if property is set either as an attribute or metadata. * @@ -67,18 +98,33 @@ abstract class ElggData implements function __isset($name) { return $this->$name !== NULL; } - + + /** + * Fetch the specified attribute + * + * @param string $name The attribute to fetch + * + * @return mixed The attribute, if it exists. Otherwise, null. + */ abstract protected function get($name); - + + /** + * Set the specified attribute + * + * @param string $name The attribute to set + * @param mixed $value The value to set it to + * + * @return bool The success of your set function? + */ abstract protected function set($name, $value); - + /** * Get a URL for this object - * + * * @return string */ abstract public function getURL(); - + /** * Save this data to the appropriate database table. * @@ -92,7 +138,7 @@ abstract class ElggData implements * @return bool */ abstract public function delete(); - + /** * Returns the UNIX epoch time that this entity was created * @@ -101,7 +147,7 @@ abstract class ElggData implements public function getTimeCreated() { return $this->time_created; } - + /* * SYSTEM LOG INTERFACE */ @@ -119,10 +165,10 @@ abstract class ElggData implements * Return the GUID of the owner of this object. * * @return int - * @deprecated 1.8 Use getOwner() instead + * @deprecated 1.8 Use getOwnerGUID() instead */ public function getObjectOwnerGUID() { - elgg_deprecated_notice("The method getObjectOwnerGUID() was deprecated in Elgg 1.8. Use getOwner() instead.", 1.8); + elgg_deprecated_notice("getObjectOwnerGUID() was deprecated. Use getOwnerGUID().", 1.8); return $this->owner_guid; } @@ -152,7 +198,7 @@ abstract class ElggData implements * * @see Iterator::current() * - * @return void + * @return mixed */ public function current() { return current($this->attributes); @@ -163,7 +209,7 @@ abstract class ElggData implements * * @see Iterator::key() * - * @return void + * @return string */ public function key() { return key($this->attributes); @@ -185,7 +231,7 @@ abstract class ElggData implements * * @see Iterator::valid() * - * @return void + * @return bool */ public function valid() { return $this->valid; @@ -223,12 +269,13 @@ abstract class ElggData implements * * @param mixed $key Name * - * @return void + * @return mixed */ public function offsetGet($key) { if (array_key_exists($key, $this->attributes)) { return $this->attributes[$key]; } + return null; } /** @@ -259,4 +306,4 @@ abstract class ElggData implements public function offsetExists($offset) { return array_key_exists($offset, $this->attributes); } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggDiskFilestore.php b/engine/classes/ElggDiskFilestore.php index 4f9aae1af..6e2354012 100644 --- a/engine/classes/ElggDiskFilestore.php +++ b/engine/classes/ElggDiskFilestore.php @@ -60,10 +60,11 @@ class ElggDiskFilestore extends ElggFilestore { $path = substr($fullname, 0, $ls); $name = substr($fullname, $ls); + // @todo $name is unused, remove it or do we need to fix something? // Try and create the directory try { - $this->make_directory_root($path); + $this->makeDirectoryRoot($path); } catch (Exception $e) { } @@ -83,7 +84,7 @@ class ElggDiskFilestore extends ElggFilestore { $mode = "a+b"; break; default: - $msg = sprintf(elgg_echo('InvalidParameterException:UnrecognisedFileMode'), $mode); + $msg = elgg_echo('InvalidParameterException:UnrecognisedFileMode', array($mode)); throw new InvalidParameterException($msg); } @@ -108,7 +109,7 @@ class ElggDiskFilestore extends ElggFilestore { * * @param resource $f File pointer resource * @param int $length The number of bytes to read - * @param inf $offset The number of bytes to start after + * @param int $offset The number of bytes to start after * * @return mixed Contents of file or false on fail. */ @@ -193,25 +194,33 @@ class ElggDiskFilestore extends ElggFilestore { } /** - * Returns the filename as saved on disk for an ElggFile object + * Get the filename as saved on disk for an ElggFile object + * + * Returns an empty string if no filename set * * @param ElggFile $file File object * * @return string The full path of where the file is stored + * @throws InvalidParameterException */ public function getFilenameOnFilestore(ElggFile $file) { - $owner = $file->getOwnerEntity(); - if (!$owner) { - $owner = get_loggedin_user(); + $owner_guid = $file->getOwnerGuid(); + if (!$owner_guid) { + $owner_guid = elgg_get_logged_in_user_guid(); } - if ((!$owner) || (!$owner->username)) { - $msg = sprintf(elgg_echo('InvalidParameterException:MissingOwner'), - $file->getFilename(), $file->guid); + if (!$owner_guid) { + $msg = elgg_echo('InvalidParameterException:MissingOwner', + array($file->getFilename(), $file->guid)); throw new InvalidParameterException($msg); } - return $this->dir_root . $this->make_file_matrix($owner->guid) . $file->getFilename(); + $filename = $file->getFilename(); + if (!$filename) { + return ''; + } + + return $this->dir_root . $this->makeFileMatrix($owner_guid) . $filename; } /** @@ -219,7 +228,7 @@ class ElggDiskFilestore extends ElggFilestore { * * @param ElggFile $file File object * - * @return mixed + * @return string */ public function grabFile(ElggFile $file) { return file_get_contents($file->getFilenameOnFilestore()); @@ -233,6 +242,9 @@ class ElggDiskFilestore extends ElggFilestore { * @return bool */ public function exists(ElggFile $file) { + if (!$file->getFilename()) { + return false; + } return file_exists($this->getFilenameOnFilestore($file)); } @@ -246,12 +258,13 @@ class ElggDiskFilestore extends ElggFilestore { */ public function getSize($prefix = '', $container_guid) { if ($container_guid) { - return get_dir_size($this->dir_root . $this->make_file_matrix($container_guid) . $prefix); + return get_dir_size($this->dir_root . $this->makeFileMatrix($container_guid) . $prefix); } else { return false; } } + // @codingStandardsIgnoreStart /** * Create a directory $dirroot * @@ -266,6 +279,7 @@ class ElggDiskFilestore extends ElggFilestore { return $this->makeDirectoryRoot($dirroot); } + // @codingStandardsIgnoreEnd /** * Create a directory $dirroot @@ -278,13 +292,14 @@ class ElggDiskFilestore extends ElggFilestore { protected function makeDirectoryRoot($dirroot) { if (!file_exists($dirroot)) { if (!@mkdir($dirroot, 0700, true)) { - throw new IOException(sprintf(elgg_echo('IOException:CouldNotMake'), $dirroot)); + throw new IOException(elgg_echo('IOException:CouldNotMake', array($dirroot))); } } return true; } + // @codingStandardsIgnoreStart /** * Multibyte string tokeniser. * @@ -315,30 +330,31 @@ class ElggDiskFilestore extends ElggFilestore { } else { return str_split($string); } - - return false; } + // @codingStandardsIgnoreEnd + // @codingStandardsIgnoreStart /** * Construct a file path matrix for an entity. * * @param int $identifier The guide of the entity to store the data under. * - * @return str The path where the entity's data will be stored. + * @return string The path where the entity's data will be stored. * @deprecated 1.8 Use ElggDiskFilestore::makeFileMatrix() */ protected function make_file_matrix($identifier) { elgg_deprecated_notice('ElggDiskFilestore::make_file_matrix() is deprecated by ::makeFileMatrix()', 1.8); - return $this->makefileMatrix($identifier); + return $this->makeFileMatrix($identifier); } + // @codingStandardsIgnoreEnd /** * Construct a file path matrix for an entity. * * @param int $guid The guide of the entity to store the data under. * - * @return str The path where the entity's data will be stored. + * @return string The path where the entity's data will be stored. */ protected function makeFileMatrix($guid) { $entity = get_entity($guid); @@ -352,6 +368,7 @@ class ElggDiskFilestore extends ElggFilestore { return "$time_created/$entity->guid/"; } + // @codingStandardsIgnoreStart /** * Construct a filename matrix. * @@ -363,13 +380,14 @@ class ElggDiskFilestore extends ElggFilestore { * * @param int $guid The entity to contrust a matrix for * - * @return str The + * @return string The */ protected function user_file_matrix($guid) { elgg_deprecated_notice('ElggDiskFilestore::user_file_matrix() is deprecated by ::makeFileMatrix()', 1.8); return $this->makeFileMatrix($guid); } + // @codingStandardsIgnoreEnd /** * Returns a list of attributes to save to the database when saving @@ -396,4 +414,4 @@ class ElggDiskFilestore extends ElggFilestore { return false; } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggEntity.php b/engine/classes/ElggEntity.php index ef38bee6b..a563f6fad 100644 --- a/engine/classes/ElggEntity.php +++ b/engine/classes/ElggEntity.php @@ -23,8 +23,18 @@ * instead of this class. * * @package Elgg.Core - * @subpackage DataMode.Entities - * @link http://docs.elgg.org/DataModel/ElggEntity + * @subpackage DataModel.Entities + * + * @property string $type object, user, group, or site (read-only after save) + * @property string $subtype Further clarifies the nature of the entity (read-only after save) + * @property int $guid The unique identifier for this entity (read only) + * @property int $owner_guid The GUID of the creator of this entity + * @property int $container_guid The GUID of the entity containing this entity + * @property int $site_guid The GUID of the website this entity is associated with + * @property int $access_id Specifies the visibility level of this entity + * @property int $time_created A UNIX timestamp of when the entity was created (read-only, set on first save) + * @property int $time_updated A UNIX timestamp of when the entity was last updated (automatically updated on save) + * @property-read string $enabled */ abstract class ElggEntity extends ElggData implements Notable, // Calendar interface @@ -55,24 +65,16 @@ abstract class ElggEntity extends ElggData implements protected $temp_annotations = array(); /** - * Volatile data structure for this object, allows for storage of data - * in-memory that isn't sync'd back to the metadata table. + * Holds private settings until entity is saved. Once the entity is saved, + * private settings are written immediately to the database. */ - protected $volatile = array(); + protected $temp_private_settings = array(); /** - * Initialise the attributes array. - * - * This is vital to distinguish between metadata and base parameters. - * - * @return void - * @deprecated 1.8 Use initializeAttributes() + * Volatile data structure for this object, allows for storage of data + * in-memory that isn't sync'd back to the metadata table. */ - protected function initialise_attributes() { - elgg_deprecated_notice('ElggEntity::initialise_attributes() is deprecated by ::initializeAttributes()', 1.8); - - $this->initializeAttributes(); - } + protected $volatile = array(); /** * Initialize the attributes array. @@ -83,20 +85,19 @@ abstract class ElggEntity extends ElggData implements */ protected function initializeAttributes() { parent::initializeAttributes(); - - initialise_entity_cache(); - $this->attributes['guid'] = ""; - $this->attributes['type'] = ""; - $this->attributes['subtype'] = ""; + $this->attributes['guid'] = NULL; + $this->attributes['type'] = NULL; + $this->attributes['subtype'] = NULL; - $this->attributes['owner_guid'] = get_loggedin_userid(); - $this->attributes['container_guid'] = get_loggedin_userid(); + $this->attributes['owner_guid'] = elgg_get_logged_in_user_guid(); + $this->attributes['container_guid'] = elgg_get_logged_in_user_guid(); - $this->attributes['site_guid'] = 0; + $this->attributes['site_guid'] = NULL; $this->attributes['access_id'] = ACCESS_PRIVATE; - $this->attributes['time_updated'] = ""; - $this->attributes['last_action'] = ''; + $this->attributes['time_created'] = NULL; + $this->attributes['time_updated'] = NULL; + $this->attributes['last_action'] = NULL; $this->attributes['enabled'] = "yes"; // There now follows a bit of a hack @@ -138,7 +139,10 @@ abstract class ElggEntity extends ElggData implements return; } - $metadata_array = get_metadata_for_entity($this->guid); + $metadata_array = elgg_get_metadata(array( + 'guid' => $this->guid, + 'limit' => 0 + )); $this->attributes['guid'] = ""; @@ -183,7 +187,7 @@ abstract class ElggEntity extends ElggData implements */ public function get($name) { // See if its in our base attributes - if (isset($this->attributes[$name])) { + if (array_key_exists($name, $this->attributes)) { return $this->attributes[$name]; } @@ -197,8 +201,11 @@ abstract class ElggEntity extends ElggData implements /** * Sets the value of a property. * - * If $name is defined in $this->attributes that value is set, otherwise it will - * set the appropriate item of metadata. + * If $name is defined in $this->attributes that value is set, otherwise it is + * saved as metadata. + * + * @warning Metadata set this way will inherit the entity's owner and access ID. If you want + * to set metadata with a different owner, use create_metadata(). * * @warning It is important that your class populates $this->attributes with keys * for all base attributes, anything not in their gets set as METADATA. @@ -219,7 +226,6 @@ abstract class ElggEntity extends ElggData implements // Certain properties should not be manually changed! switch ($name) { case 'guid': - case 'time_created': case 'time_updated': case 'last_action': return FALSE; @@ -243,21 +249,53 @@ abstract class ElggEntity extends ElggData implements * @return mixed The value, or NULL if not found. */ public function getMetaData($name) { - if ((int) ($this->guid) > 0) { - $md = get_metadata_byname($this->getGUID(), $name); - } else { + $guid = $this->getGUID(); + + if (! $guid) { if (isset($this->temp_metadata[$name])) { - return $this->temp_metadata[$name]; + // md is returned as an array only if more than 1 entry + if (count($this->temp_metadata[$name]) == 1) { + return $this->temp_metadata[$name][0]; + } else { + return $this->temp_metadata[$name]; + } + } else { + return null; + } + } + + // upon first cache miss, just load/cache all the metadata and retry. + // if this works, the rest of this function may not be needed! + $cache = elgg_get_metadata_cache(); + if ($cache->isKnown($guid, $name)) { + return $cache->load($guid, $name); + } else { + $cache->populateFromEntities(array($guid)); + // in case ignore_access was on, we have to check again... + if ($cache->isKnown($guid, $name)) { + return $cache->load($guid, $name); } } + $md = elgg_get_metadata(array( + 'guid' => $guid, + 'metadata_name' => $name, + 'limit' => 0, + )); + + $value = null; + if ($md && !is_array($md)) { - return $md->value; + $value = $md->value; + } elseif (count($md) == 1) { + $value = $md[0]->value; } else if ($md && is_array($md)) { - return metadata_array_to_values($md); + $value = metadata_array_to_values($md); } - return null; + $cache->save($guid, $name, $value); + + return $value; } /** @@ -273,73 +311,136 @@ abstract class ElggEntity extends ElggData implements if (array_key_exists($name, $this->attributes)) { $this->attributes[$name] = ""; } else { - $this->clearMetaData($name); + $this->deleteMetadata($name); } } /** * Set a piece of metadata. * - * @tip Plugin authors should use the magic methods. + * Plugin authors should use the magic methods or create_metadata(). + * + * @warning The metadata will inherit the parent entity's owner and access ID. + * If you want to write metadata with a different owner, use create_metadata(). * * @access private * * @param string $name Name of the metadata - * @param mixed $value Value of the metadata + * @param mixed $value Value of the metadata (doesn't support assoc arrays) * @param string $value_type Types supported: integer and string. Will auto-identify if not set * @param bool $multiple Allow multiple values for a single name (doesn't support assoc arrays) * * @return bool */ - public function setMetaData($name, $value, $value_type = "", $multiple = false) { + public function setMetaData($name, $value, $value_type = null, $multiple = false) { + + // normalize value to an array that we will loop over + // remove indexes if value already an array. if (is_array($value)) { - unset($this->temp_metadata[$name]); - remove_metadata($this->getGUID(), $name); - foreach ($value as $v) { - if ((int) $this->guid > 0) { - $multiple = true; - if (!create_metadata($this->getGUID(), $name, $v, $value_type, - $this->getOwner(), $this->getAccessID(), $multiple)) { - return false; - } - } else { - if (($multiple) && (isset($this->temp_metadata[$name]))) { - if (!is_array($this->temp_metadata[$name])) { - $tmp = $this->temp_metadata[$name]; - $this->temp_metadata[$name] = array(); - $this->temp_metadata[$name][] = $tmp; - } - - $this->temp_metadata[$name][] = $value; - } else { - $this->temp_metadata[$name] = $value; - } + $value = array_values($value); + } else { + $value = array($value); + } + + // saved entity. persist md to db. + if ($this->guid) { + // if overwriting, delete first. + if (!$multiple) { + $options = array( + 'guid' => $this->getGUID(), + 'metadata_name' => $name, + 'limit' => 0 + ); + // @todo in 1.9 make this return false if can't add metadata + // https://github.com/elgg/elgg/issues/4520 + // + // need to remove access restrictions right now to delete + // because this is the expected behavior + $ia = elgg_set_ignore_access(true); + if (false === elgg_delete_metadata($options)) { + return false; } + elgg_set_ignore_access($ia); } - return true; - } else { - unset($this->temp_metadata[$name]); - if ((int) $this->guid > 0) { - $result = create_metadata($this->getGUID(), $name, $value, $value_type, - $this->getOwner(), $this->getAccessID(), $multiple); - return (bool)$result; - } else { - if (($multiple) && (isset($this->temp_metadata[$name]))) { - if (!is_array($this->temp_metadata[$name])) { - $tmp = $this->temp_metadata[$name]; - $this->temp_metadata[$name] = array(); - $this->temp_metadata[$name][] = $tmp; - } - - $this->temp_metadata[$name][] = $value; - } else { - $this->temp_metadata[$name] = $value; + // add new md + $result = true; + foreach ($value as $value_tmp) { + // at this point $value should be appended because it was cleared above if needed. + $md_id = create_metadata($this->getGUID(), $name, $value_tmp, $value_type, + $this->getOwnerGUID(), $this->getAccessId(), true); + if (!$md_id) { + return false; } + } - return true; + return $result; + } else { + // unsaved entity. store in temp array + // returning single entries instead of an array of 1 element is decided in + // getMetaData(), just like pulling from the db. + // + // if overwrite, delete first + if (!$multiple || !isset($this->temp_metadata[$name])) { + $this->temp_metadata[$name] = array(); } + + // add new md + $this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value); + return true; + } + } + + /** + * Deletes all metadata on this object (metadata.entity_guid = $this->guid). + * If you pass a name, only metadata matching that name will be deleted. + * + * @warning Calling this with no $name will clear all metadata on the entity. + * + * @param null|string $name The name of the metadata to remove. + * @return bool + * @since 1.8 + */ + public function deleteMetadata($name = null) { + + if (!$this->guid) { + return false; + } + + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['metadata_name'] = $name; + } + + return elgg_delete_metadata($options); + } + + /** + * Deletes all metadata owned by this object (metadata.owner_guid = $this->guid). + * If you pass a name, only metadata matching that name will be deleted. + * + * @param null|string $name The name of metadata to delete. + * @return bool + * @since 1.8 + */ + public function deleteOwnedMetadata($name = null) { + // access is turned off for this because they might + // no longer have access to an entity they created metadata on. + $ia = elgg_set_ignore_access(true); + $options = array( + 'metadata_owner_guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['metadata_name'] = $name; } + + $r = elgg_delete_metadata($options); + elgg_set_ignore_access($ia); + return $r; } /** @@ -348,17 +449,53 @@ abstract class ElggEntity extends ElggData implements * @warning Calling this with no or empty arguments will clear all metadata on the entity. * * @param string $name The name of the metadata to clear - * * @return mixed bool + * @deprecated 1.8 Use deleteMetadata() */ - public function clearMetaData($name = "") { - if (empty($name)) { - return clear_metadata($this->getGUID()); - } else { - return remove_metadata($this->getGUID(), $name); + public function clearMetaData($name = '') { + elgg_deprecated_notice('ElggEntity->clearMetadata() is deprecated by ->deleteMetadata()', 1.8); + return $this->deleteMetadata($name); + } + + /** + * Disables metadata for this entity, optionally based on name. + * + * @param string $name An options name of metadata to disable. + * @return bool + * @since 1.8 + */ + public function disableMetadata($name = '') { + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['metadata_name'] = $name; } + + return elgg_disable_metadata($options); } + /** + * Enables metadata for this entity, optionally based on name. + * + * @warning Before calling this, you must use {@link access_show_hidden_entities()} + * + * @param string $name An options name of metadata to enable. + * @return bool + * @since 1.8 + */ + public function enableMetadata($name = '') { + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['metadata_name'] = $name; + } + + return elgg_enable_metadata($options); + } /** * Get a piece of volatile (non-persisted) data on this entity. @@ -379,7 +516,6 @@ abstract class ElggEntity extends ElggData implements } } - /** * Set a piece of volatile (non-persisted) data on this entity * @@ -396,7 +532,6 @@ abstract class ElggEntity extends ElggData implements $this->volatile[$name] = $value; } - /** * Remove all relationships to and from this entity. * @@ -405,13 +540,26 @@ abstract class ElggEntity extends ElggData implements * @see ElggEntity::addRelationship() * @see ElggEntity::removeRelationship() */ - public function clearRelationships() { + public function deleteRelationships() { remove_entity_relationships($this->getGUID()); remove_entity_relationships($this->getGUID(), "", true); return true; } /** + * Remove all relationships to and from this entity. + * + * @return bool + * @see ElggEntity::addRelationship() + * @see ElggEntity::removeRelationship() + * @deprecated 1.8 Use ->deleteRelationship() + */ + public function clearRelationships() { + elgg_deprecated_notice('ElggEntity->clearRelationships() is deprecated by ->deleteRelationships()', 1.8); + return $this->deleteRelationships(); + } + + /** * Add a relationship between this an another entity. * * @tip Read the relationship like "$guid is a $relationship of this entity." @@ -451,10 +599,14 @@ abstract class ElggEntity extends ElggData implements * @param mixed $value Value of private setting * * @return bool - * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings */ function setPrivateSetting($name, $value) { - return set_private_setting($this->getGUID(), $name, $value); + if ((int) $this->guid > 0) { + return set_private_setting($this->getGUID(), $name, $value); + } else { + $this->temp_private_settings[$name] = $value; + return true; + } } /** @@ -465,7 +617,14 @@ abstract class ElggEntity extends ElggData implements * @return mixed */ function getPrivateSetting($name) { - return get_private_setting($this->getGUID(), $name); + if ((int) ($this->guid) > 0) { + return get_private_setting($this->getGUID(), $name); + } else { + if (isset($this->temp_private_settings[$name])) { + return $this->temp_private_settings[$name]; + } + } + return null; } /** @@ -480,10 +639,117 @@ abstract class ElggEntity extends ElggData implements } /** + * Deletes all annotations on this object (annotations.entity_guid = $this->guid). + * If you pass a name, only annotations matching that name will be deleted. + * + * @warning Calling this with no or empty arguments will clear all annotations on the entity. + * + * @param null|string $name The annotations name to remove. + * @return bool + * @since 1.8 + */ + public function deleteAnnotations($name = null) { + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['annotation_name'] = $name; + } + + return elgg_delete_annotations($options); + } + + /** + * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid). + * If you pass a name, only annotations matching that name will be deleted. + * + * @param null|string $name The name of annotations to delete. + * @return bool + * @since 1.8 + */ + public function deleteOwnedAnnotations($name = null) { + // access is turned off for this because they might + // no longer have access to an entity they created annotations on. + $ia = elgg_set_ignore_access(true); + $options = array( + 'annotation_owner_guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['annotation_name'] = $name; + } + + $r = elgg_delete_annotations($options); + elgg_set_ignore_access($ia); + return $r; + } + + /** + * Disables annotations for this entity, optionally based on name. + * + * @param string $name An options name of annotations to disable. + * @return bool + * @since 1.8 + */ + public function disableAnnotations($name = '') { + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['annotation_name'] = $name; + } + + return elgg_disable_annotations($options); + } + + /** + * Enables annotations for this entity, optionally based on name. + * + * @warning Before calling this, you must use {@link access_show_hidden_entities()} + * + * @param string $name An options name of annotations to enable. + * @return bool + * @since 1.8 + */ + public function enableAnnotations($name = '') { + $options = array( + 'guid' => $this->guid, + 'limit' => 0 + ); + if ($name) { + $options['annotation_name'] = $name; + } + + return elgg_enable_annotations($options); + } + + /** + * Helper function to return annotation calculation results + * + * @param string $name The annotation name. + * @param string $calculation A valid MySQL function to run its values through + * @return mixed + */ + private function getAnnotationCalculation($name, $calculation) { + $options = array( + 'guid' => $this->getGUID(), + 'annotation_name' => $name, + 'annotation_calculation' => $calculation + ); + + return elgg_get_annotations($options); + } + + /** * Adds an annotation to an entity. * * @warning By default, annotations are private. * + * @warning Annotating an unsaved entity more than once with the same name + * will only save the last annotation. + * * @param string $name Annotation name * @param mixed $value Annotation value * @param int $access_id Access ID @@ -491,8 +757,6 @@ abstract class ElggEntity extends ElggData implements * @param string $vartype The type of annotation value * * @return bool - * - * @link http://docs.elgg.org/DataModel/Annotations */ function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_id = 0, $vartype = "") { if ((int) $this->guid > 0) { @@ -509,15 +773,29 @@ abstract class ElggEntity extends ElggData implements * @param string $name Annotation name * @param int $limit Limit * @param int $offset Offset - * @param string $order asc or desc + * @param string $order Order by time: asc or desc * * @return array */ function getAnnotations($name, $limit = 50, $offset = 0, $order = "asc") { if ((int) ($this->guid) > 0) { - return get_annotations($this->getGUID(), "", "", $name, "", 0, $limit, $offset, $order); + + $options = array( + 'guid' => $this->guid, + 'annotation_name' => $name, + 'limit' => $limit, + 'offset' => $offset, + ); + + if ($order != 'asc') { + $options['reverse_order_by'] = true; + } + + return elgg_get_annotations($options); + } else if (isset($this->temp_annotations[$name])) { + return array($this->temp_annotations[$name]); } else { - return $this->temp_annotations[$name]; + return array(); } } @@ -528,11 +806,12 @@ abstract class ElggEntity extends ElggData implements * all annotations on the entity. * * @param string $name Annotation name - * * @return bool + * @deprecated 1.8 Use ->deleteAnnotations() */ function clearAnnotations($name = "") { - return clear_annotations($this->getGUID(), $name); + elgg_deprecated_notice('ElggEntity->clearAnnotations() is deprecated by ->deleteAnnotations()', 1.8); + return $this->deleteAnnotations($name); } /** @@ -543,7 +822,7 @@ abstract class ElggEntity extends ElggData implements * @return int */ function countAnnotations($name = "") { - return count_annotations($this->getGUID(), "", "", $name); + return $this->getAnnotationCalculation($name, 'count'); } /** @@ -554,7 +833,7 @@ abstract class ElggEntity extends ElggData implements * @return int */ function getAnnotationsAvg($name) { - return get_annotations_avg($this->getGUID(), "", "", $name); + return $this->getAnnotationCalculation($name, 'avg'); } /** @@ -565,7 +844,7 @@ abstract class ElggEntity extends ElggData implements * @return int */ function getAnnotationsSum($name) { - return get_annotations_sum($this->getGUID(), "", "", $name); + return $this->getAnnotationCalculation($name, 'sum'); } /** @@ -576,7 +855,7 @@ abstract class ElggEntity extends ElggData implements * @return int */ function getAnnotationsMin($name) { - return get_annotations_min($this->getGUID(), "", "", $name); + return $this->getAnnotationCalculation($name, 'min'); } /** @@ -587,7 +866,24 @@ abstract class ElggEntity extends ElggData implements * @return int */ function getAnnotationsMax($name) { - return get_annotations_max($this->getGUID(), "", "", $name); + return $this->getAnnotationCalculation($name, 'max'); + } + + /** + * Count the number of comments attached to this entity. + * + * @return int Number of comments + * @since 1.8.0 + */ + function countComments() { + $params = array('entity' => $this); + $num = elgg_trigger_plugin_hook('comments:count', $this->getType(), $params); + + if (is_int($num)) { + return $num; + } else { + return $this->getAnnotationCalculation('generic_comment', 'count'); + } } /** @@ -644,21 +940,78 @@ abstract class ElggEntity extends ElggData implements * @param ElggMetadata $metadata The piece of metadata to specifically check * @param int $user_guid The user GUID, optionally (default: logged in user) * - * @return true|false + * @return bool */ function canEditMetadata($metadata = null, $user_guid = 0) { return can_edit_entity_metadata($this->getGUID(), $user_guid, $metadata); } /** - * Can a user write to this entity's container. + * Can a user add an entity to this container + * + * @param int $user_guid The user. + * @param string $type The type of entity we're looking to write + * @param string $subtype The subtype of the entity we're looking to write + * + * @return bool + */ + public function canWriteToContainer($user_guid = 0, $type = 'all', $subtype = 'all') { + return can_write_to_container($user_guid, $this->guid, $type, $subtype); + } + + /** + * Can a user comment on an entity? + * + * @tip Can be overridden by registering for the permissions_check:comment, + * <entity type> plugin hook. + * + * @param int $user_guid User guid (default is logged in user) + * + * @return bool + */ + public function canComment($user_guid = 0) { + if ($user_guid == 0) { + $user_guid = elgg_get_logged_in_user_guid(); + } + $user = get_entity($user_guid); + + // By default, we don't take a position of whether commenting is allowed + // because it is handled by the subclasses of ElggEntity + $params = array('entity' => $this, 'user' => $user); + return elgg_trigger_plugin_hook('permissions_check:comment', $this->type, $params, null); + } + + /** + * Can a user annotate an entity? * - * @param int $user_guid The user. + * @tip Can be overridden by registering for the permissions_check:annotate, + * <entity type> plugin hook. + * + * @tip If you want logged out users to annotate an object, do not call + * canAnnotate(). It's easier than using the plugin hook. + * + * @param int $user_guid User guid (default is logged in user) + * @param string $annotation_name The name of the annotation (default is unspecified) * * @return bool */ - public function canWriteToContainer($user_guid = 0) { - return can_write_to_container($user_guid, $this->getGUID()); + public function canAnnotate($user_guid = 0, $annotation_name = '') { + if ($user_guid == 0) { + $user_guid = elgg_get_logged_in_user_guid(); + } + $user = get_entity($user_guid); + + $return = true; + if (!$user) { + $return = false; + } + + $params = array( + 'entity' => $this, + 'user' => $user, + 'annotation_name' => $annotation_name, + ); + return elgg_trigger_plugin_hook('permissions_check:annotate', $this->type, $params, $return); } /** @@ -673,7 +1026,7 @@ abstract class ElggEntity extends ElggData implements /** * Returns the guid. * - * @return int GUID + * @return int|null GUID */ public function getGUID() { return $this->get('guid'); @@ -705,18 +1058,29 @@ abstract class ElggEntity extends ElggData implements } /** + * Get the guid of the entity's owner. + * + * @return int The owner GUID + */ + public function getOwnerGUID() { + return $this->owner_guid; + } + + /** * Return the guid of the entity's owner. * * @return int The owner GUID + * @deprecated 1.8 Use getOwnerGUID() */ public function getOwner() { - return $this->owner_guid; + elgg_deprecated_notice("ElggEntity::getOwner deprecated for ElggEntity::getOwnerGUID", 1.8); + return $this->getOwnerGUID(); } /** - * Returns the ElggEntity or child object of the owner of the entity. + * Gets the ElggEntity that owns this entity. * - * @return ElggEntity The owning user + * @return ElggEntity The owning entity */ public function getOwnerEntity() { return get_entity($this->owner_guid); @@ -729,30 +1093,57 @@ abstract class ElggEntity extends ElggData implements * * @return bool */ + public function setContainerGUID($container_guid) { + $container_guid = (int)$container_guid; + + return $this->set('container_guid', $container_guid); + } + + /** + * Set the container for this object. + * + * @param int $container_guid The ID of the container. + * + * @return bool + * @deprecated 1.8 use setContainerGUID() + */ public function setContainer($container_guid) { + elgg_deprecated_notice("ElggObject::setContainer deprecated for ElggEntity::setContainerGUID", 1.8); $container_guid = (int)$container_guid; return $this->set('container_guid', $container_guid); } /** - * Returns the container GUID of this object. + * Gets the container GUID for this entity. * * @return int */ + public function getContainerGUID() { + return $this->get('container_guid'); + } + + /** + * Gets the container GUID for this entity. + * + * @return int + * @deprecated 1.8 Use getContainerGUID() + */ public function getContainer() { + elgg_deprecated_notice("ElggObject::getContainer deprecated for ElggEntity::getContainerGUID", 1.8); return $this->get('container_guid'); } /** - * Returns the container entity for this object. + * Get the container entity for this object. * * @return ElggEntity + * @since 1.8.0 */ public function getContainerEntity() { - return get_entity($this->getContainer()); + return get_entity($this->getContainerGUID()); } - + /** * Returns the UNIX epoch time that this entity was last updated * @@ -791,18 +1182,49 @@ abstract class ElggEntity extends ElggData implements } /** + * Get the URL for this entity's icon + * + * Plugins can register for the 'entity:icon:url', <type> plugin hook + * to customize the icon for an entity. + * + * @param string $size Size of the icon: tiny, small, medium, large + * + * @return string The URL + * @since 1.8.0 + */ + public function getIconURL($size = 'medium') { + $size = elgg_strtolower($size); + + if (isset($this->icon_override[$size])) { + elgg_deprecated_notice("icon_override on an individual entity is deprecated", 1.8); + return $this->icon_override[$size]; + } + + $type = $this->getType(); + $params = array( + 'entity' => $this, + 'size' => $size, + ); + + $url = elgg_trigger_plugin_hook('entity:icon:url', $type, $params, null); + if ($url == null) { + $url = "_graphics/icons/default/$size.png"; + } + + return elgg_normalize_url($url); + } + + /** * Returns a URL for the entity's icon. * * @param string $size Either 'large', 'medium', 'small' or 'tiny' * * @return string The url or false if no url could be worked out. - * @see get_entity_icon_url() + * @deprecated Use getIconURL() */ public function getIcon($size = 'medium') { - if (isset($this->icon_override[$size])) { - return $this->icon_override[$size]; - } - return get_entity_icon_url($this, $size); + elgg_deprecated_notice("getIcon() deprecated by getIconURL()", 1.8); + return $this->getIconURL($size); } /** @@ -814,8 +1236,11 @@ abstract class ElggEntity extends ElggData implements * @param string $size The size its for. * * @return bool + * @deprecated 1.8 See getIconURL() for the plugin hook to use */ public function setIcon($url, $size = 'medium') { + elgg_deprecated_notice("icon_override on an individual entity is deprecated", 1.8); + $url = sanitise_string($url); $size = sanitise_string($size); @@ -837,22 +1262,31 @@ abstract class ElggEntity extends ElggData implements } /** - * Save attributes to the entities table. + * Save an entity. * - * @return bool + * @return bool|int * @throws IOException */ public function save() { - $guid = (int) $this->guid; + $guid = $this->getGUID(); if ($guid > 0) { - cache_entity($this); - return update_entity( - $this->get('guid'), + // See #5600. This ensures the lower level can_edit_entity() check will use a + // fresh entity from the DB so it sees the persisted owner_guid + _elgg_disable_caching_for_entity($guid); + + $ret = update_entity( + $guid, $this->get('owner_guid'), $this->get('access_id'), - $this->get('container_guid') + $this->get('container_guid'), + $this->get('time_created') ); + + _elgg_enable_caching_for_entity($guid); + _elgg_cache_entity($this); + + return $ret; } else { // Create a new entity (nb: using attribute array directly // 'cos set function does something special!) @@ -874,8 +1308,7 @@ abstract class ElggEntity extends ElggData implements } } - // Save any unsaved annotations metadata. - // @todo How to capture extra information (access id etc) + // Save any unsaved annotations. if (sizeof($this->temp_annotations) > 0) { foreach ($this->temp_annotations as $name => $value) { $this->annotate($name, $value); @@ -883,14 +1316,19 @@ abstract class ElggEntity extends ElggData implements } } + // Save any unsaved private settings. + if (sizeof($this->temp_private_settings) > 0) { + foreach ($this->temp_private_settings as $name => $value) { + $this->setPrivateSetting($name, $value); + unset($this->temp_private_settings[$name]); + } + } + // set the subtype to id now rather than a string $this->attributes['subtype'] = get_subtype_id($this->attributes['type'], $this->attributes['subtype']); - // Cache object handle - if ($this->attributes['guid']) { - cache_entity($this); - } + _elgg_cache_entity($this); return $this->attributes['guid']; } @@ -899,12 +1337,16 @@ abstract class ElggEntity extends ElggData implements /** * Loads attributes from the entities table into the object. * - * @param int $guid GUID of Entity + * @param mixed $guid GUID of entity or stdClass object from entities table * * @return bool */ protected function load($guid) { - $row = get_entity_as_row($guid); + if ($guid instanceof stdClass) { + $row = $guid; + } else { + $row = get_entity_as_row($guid); + } if ($row) { // Create the array if necessary - all subclasses should test before creating @@ -923,9 +1365,12 @@ abstract class ElggEntity extends ElggData implements $this->attributes['tables_loaded']++; } + // guid needs to be an int https://github.com/elgg/elgg/issues/4111 + $this->attributes['guid'] = (int)$this->attributes['guid']; + // Cache object handle if ($this->attributes['guid']) { - cache_entity($this); + _elgg_cache_entity($this); } return true; @@ -994,10 +1439,12 @@ abstract class ElggEntity extends ElggData implements /** * Delete this entity. * + * @param bool $recursive Whether to delete all the entities contained by this entity + * * @return bool */ - public function delete() { - return delete_entity($this->get('guid')); + public function delete($recursive = true) { + return delete_entity($this->get('guid'), $recursive); } /* @@ -1005,19 +1452,25 @@ abstract class ElggEntity extends ElggData implements */ /** + * Gets the 'location' metadata for the entity + * + * @return string The location + */ + public function getLocation() { + return $this->location; + } + + /** * Sets the 'location' metadata for the entity * * @todo Unimplemented * * @param string $location String representation of the location * - * @return true + * @return bool */ public function setLocation($location) { - $location = sanitise_string($location); - $this->location = $location; - return true; } @@ -1027,13 +1480,10 @@ abstract class ElggEntity extends ElggData implements * @param float $lat Latitude * @param float $long Longitude * - * @return true + * @return bool * @todo Unimplemented */ public function setLatLong($lat, $long) { - $lat = sanitise_string($lat); - $long = sanitise_string($long); - $this->set('geo:lat', $lat); $this->set('geo:long', $long); @@ -1043,29 +1493,20 @@ abstract class ElggEntity extends ElggData implements /** * Return the entity's latitude. * - * @return int + * @return float * @todo Unimplemented */ public function getLatitude() { - return $this->get('geo:lat'); + return (float)$this->get('geo:lat'); } /** * Return the entity's longitude * - * @return Int + * @return float */ public function getLongitude() { - return $this->get('geo:long'); - } - - /** - * Return the entity's location - * - * @return string - */ - public function getLocation() { - return $this->get('location'); + return (float)$this->get('geo:long'); } /* @@ -1172,36 +1613,36 @@ abstract class ElggEntity extends ElggData implements foreach ($this->attributes as $k => $v) { $meta = NULL; - if (in_array( $k, $exportable_values)) { + if (in_array($k, $exportable_values)) { switch ($k) { - case 'guid' : // Dont use guid in OpenDD - case 'type' : // Type and subtype already taken care of - case 'subtype' : - break; + case 'guid': // Dont use guid in OpenDD + case 'type': // Type and subtype already taken care of + case 'subtype': + break; - case 'time_created' : // Created = published + case 'time_created': // Created = published $odd->setAttribute('published', date("r", $v)); - break; + break; - case 'site_guid' : // Container + case 'site_guid': // Container $k = 'site_uuid'; $v = guid_to_uuid($v); $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); - break; + break; - case 'container_guid' : // Container + case 'container_guid': // Container $k = 'container_uuid'; $v = guid_to_uuid($v); $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); - break; + break; - case 'owner_guid' : // Convert owner guid to uuid, this will be stored in metadata + case 'owner_guid': // Convert owner guid to uuid, this will be stored in metadata $k = 'owner_uuid'; $v = guid_to_uuid($v); $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); - break; + break; - default : + default: $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); } @@ -1219,7 +1660,7 @@ abstract class ElggEntity extends ElggData implements */ elgg_set_viewtype('default'); - $view = elgg_view_entity($this, true); + $view = elgg_view_entity($this, array('full_view' => true)); elgg_set_viewtype(); $tmp[] = new ODDMetaData($uuid . "volatile/renderedentity/", $uuid, @@ -1235,9 +1676,11 @@ abstract class ElggEntity extends ElggData implements /** * Import data from an parsed ODD xml data array. * - * @param array $data XML data + * @param ODD $data XML data * * @return true + * + * @throws InvalidParameterException */ public function import(ODD $data) { if (!($data instanceof ODDEntity)) { @@ -1249,7 +1692,7 @@ abstract class ElggEntity extends ElggData implements $this->attributes['subtype'] = $data->getAttribute('subclass'); // Set owner - $this->attributes['owner_guid'] = get_loggedin_userid(); // Import as belonging to importer. + $this->attributes['owner_guid'] = elgg_get_logged_in_user_guid(); // Import as belonging to importer. // Set time $this->attributes['time_created'] = strtotime($data->getAttribute('published')); @@ -1299,8 +1742,6 @@ abstract class ElggEntity extends ElggData implements * @return array */ public function getTags($tag_names = NULL) { - global $CONFIG; - if ($tag_names && !is_array($tag_names)) { $tag_names = array($tag_names); } @@ -1326,4 +1767,4 @@ abstract class ElggEntity extends ElggData implements return $entity_tags; } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggExtender.php b/engine/classes/ElggExtender.php index 8d0f4bd66..25aba354f 100644 --- a/engine/classes/ElggExtender.php +++ b/engine/classes/ElggExtender.php @@ -3,8 +3,7 @@ * The base class for ElggEntity extenders. * * Extenders allow you to attach extended information to an - * ElggEntity. Core supports two: ElggAnnotation, ElggMetadata, - * and ElggRelationship + * ElggEntity. Core supports two: ElggAnnotation and ElggMetadata. * * Saving the extender data to database is handled by the child class. * @@ -16,15 +15,30 @@ * @link http://docs.elgg.org/DataModel/Extenders * @see ElggAnnotation * @see ElggMetadata + * + * @property string $type annotation or metadata (read-only after save) + * @property int $id The unique identifier (read-only) + * @property int $entity_guid The GUID of the entity that this extender describes + * @property int $access_id Specifies the visibility level of this extender + * @property string $name The name of this extender + * @property mixed $value The value of the extender (int or string) + * @property int $time_created A UNIX timestamp of when the extender was created (read-only, set on first save) */ -abstract class ElggExtender extends ElggData -{ +abstract class ElggExtender extends ElggData { + + /** + * (non-PHPdoc) + * + * @see ElggData::initializeAttributes() + * + * @return void + */ protected function initializeAttributes() { parent::initializeAttributes(); - - $this->attributes['type'] = ''; + + $this->attributes['type'] = NULL; } - + /** * Returns an attribute * @@ -33,7 +47,7 @@ abstract class ElggExtender extends ElggData * @return mixed */ protected function get($name) { - if (isset($this->attributes[$name])) { + if (array_key_exists($name, $this->attributes)) { // Sanitise value if necessary if ($name == 'value') { switch ($this->attributes['value_type']) { @@ -48,8 +62,8 @@ abstract class ElggExtender extends ElggData break; default : - $msg = sprintf(elgg_echo('InstallationException:TypeNotSupported'), - $this->attributes['value_type']); + $msg = elgg_echo('InstallationException:TypeNotSupported', array( + $this->attributes['value_type'])); throw new InstallationException($msg); break; @@ -80,27 +94,38 @@ abstract class ElggExtender extends ElggData } /** + * Get the GUID of the extender's owner entity. + * + * @return int The owner GUID + */ + public function getOwnerGUID() { + return $this->owner_guid; + } + + /** * Return the guid of the entity's owner. * * @return int The owner GUID + * @deprecated 1.8 Use getOwnerGUID */ public function getOwner() { - return $this->owner_guid; + elgg_deprecated_notice("ElggExtender::getOwner deprecated for ElggExtender::getOwnerGUID", 1.8); + return $this->getOwnerGUID(); } /** - * Returns the ElggEntity or child object of the owner of the entity. + * Get the entity that owns this extender * - * @return ElggEntity The owning user + * @return ElggEntity */ public function getOwnerEntity() { return get_entity($this->owner_guid); } - + /** - * Return the entity this describes. + * Get the entity this describes. * - * @return ElggEntity The enttiy + * @return ElggEntity The entity */ public function getEntity() { return get_entity($this->entity_guid); @@ -146,7 +171,7 @@ abstract class ElggExtender extends ElggData public function export() { $uuid = get_uuid_from_object($this); - $meta = new ODDMetadata($uuid, guid_to_uuid($this->entity_guid), $this->attributes['name'], + $meta = new ODDMetaData($uuid, guid_to_uuid($this->entity_guid), $this->attributes['name'], $this->attributes['value'], $this->attributes['type'], guid_to_uuid($this->owner_guid)); $meta->setAttribute('published', date("r", $this->time_created)); diff --git a/engine/classes/ElggFile.php b/engine/classes/ElggFile.php index 46cd0b6a0..23080834b 100644 --- a/engine/classes/ElggFile.php +++ b/engine/classes/ElggFile.php @@ -31,18 +31,6 @@ class ElggFile extends ElggObject { * Set subtype to 'file'. * * @return void - * - * @deprecated 1.8 Use initializeAttributes() - */ - protected function initialise_attributes() { - elgg_deprecated_notice('ElggFile::initialise_attributes() is deprecated by ::initializeAttributes()', 1.8); - $this->initializeAttributes(); - } - - /** - * Set subtype to 'file'. - * - * @return void */ protected function initializeAttributes() { parent::initializeAttributes(); @@ -105,6 +93,7 @@ class ElggFile extends ElggObject { $container_guid = $this->container_guid; } $fs = $this->getFilestore(); + // @todo add getSize() to ElggFilestore return $fs->getSize($prefix, $container_guid); } @@ -133,6 +122,49 @@ class ElggFile extends ElggObject { } /** + * Detects mime types based on filename or actual file. + * + * @param mixed $file The full path of the file to check. For uploaded files, use tmp_name. + * @param mixed $default A default. Useful to pass what the browser thinks it is. + * @since 1.7.12 + * + * @note If $file is provided, this may be called statically + * + * @return mixed Detected type on success, false on failure. + */ + public function detectMimeType($file = null, $default = null) { + if (!$file) { + if (isset($this) && $this->filename) { + $file = $this->filename; + } else { + return false; + } + } + + $mime = false; + + // for PHP5 folks. + if (function_exists('finfo_file') && defined('FILEINFO_MIME_TYPE')) { + $resource = finfo_open(FILEINFO_MIME_TYPE); + if ($resource) { + $mime = finfo_file($resource, $file); + } + } + + // for everyone else. + if (!$mime && function_exists('mime_content_type')) { + $mime = mime_content_type($file); + } + + // default + if (!$mime) { + return $default; + } + + return $mime; + } + + /** * Set the optional file description. * * @param string $description The description. @@ -149,6 +181,8 @@ class ElggFile extends ElggObject { * @param string $mode Either read/write/append * * @return resource File handler + * + * @throws IOException|InvalidParameterException */ public function open($mode) { if (!$this->getFilename()) { @@ -164,7 +198,7 @@ class ElggFile extends ElggObject { ($mode != "write") && ($mode != "append") ) { - $msg = sprintf(elgg_echo('InvalidParameterException:UnrecognisedFileMode'), $mode); + $msg = elgg_echo('InvalidParameterException:UnrecognisedFileMode', array($mode)); throw new InvalidParameterException($msg); } @@ -241,9 +275,14 @@ class ElggFile extends ElggObject { */ public function delete() { $fs = $this->getFilestore(); - if ($fs->delete($this)) { - return parent::delete(); + + $result = $fs->delete($this); + + if ($this->getGUID() && $result) { + $result = parent::delete(); } + + return $result; } /** @@ -256,6 +295,7 @@ class ElggFile extends ElggObject { public function seek($position) { $fs = $this->getFilestore(); + // @todo add seek() to ElggFilestore return $fs->seek($this->handle, $position); } @@ -314,10 +354,12 @@ class ElggFile extends ElggObject { /** * Return a filestore suitable for saving this file. - * This filestore is either a pre-registered filestore, a filestore loaded from metatags saved - * along side this file, or the system default. + * This filestore is either a pre-registered filestore, + * a filestore as recorded in metadata or the system default. * * @return ElggFilestore + * + * @throws ClassNotFoundException */ protected function getFilestore() { // Short circuit if already set. @@ -325,40 +367,43 @@ class ElggFile extends ElggObject { return $this->filestore; } - // If filestore meta set then retrieve filestore - // @todo Better way of doing this? - $metas = get_metadata_for_entity($this->guid); - $parameters = array(); - if (is_array($metas)) { - foreach ($metas as $meta) { - if (strpos($meta->name, "filestore::") !== false) { - // Filestore parameter tag - $comp = explode("::", $meta->name); - $name = $comp[1]; - - $parameters[$name] = $meta->value; + // ask for entity specific filestore + // saved as filestore::className in metadata. + // need to get all filestore::* metadata because the rest are "parameters" that + // get passed to filestore::setParameters() + if ($this->guid) { + $options = array( + 'guid' => $this->guid, + 'where' => array("n.string LIKE 'filestore::%'"), + ); + + $mds = elgg_get_metadata($options); + + $parameters = array(); + foreach ($mds as $md) { + list($foo, $name) = explode("::", $md->name); + if ($name == 'filestore') { + $filestore = $md->value; } + $parameters[$name] = $md->value; } } - if (isset($parameters['filestore'])) { - if (!class_exists($parameters['filestore'])) { - $msg = sprintf(elgg_echo('ClassNotFoundException:NotFoundNotSavedWithFile'), - $parameters['filestore'], - $this->guid); + // need to check if filestore is set because this entity is loaded in save() + // before the filestore metadata is saved. + if (isset($filestore)) { + if (!class_exists($filestore)) { + $msg = elgg_echo('ClassNotFoundException:NotFoundNotSavedWithFile', + array($filestore, $this->guid)); throw new ClassNotFoundException($msg); } - // Create new filestore object - $this->filestore = new $parameters['filestore'](); - + $this->filestore = new $filestore(); $this->filestore->setParameters($parameters); - } else { - // @todo - should we log error if filestore not set + // @todo explain why $parameters will always be set here (PhpStorm complains) } - - // if still nothing then set filestore to default + // this means the entity hasn't been saved so fallback to default if (!$this->filestore) { $this->filestore = get_default_filestore(); } diff --git a/engine/classes/ElggFileCache.php b/engine/classes/ElggFileCache.php index 2072af620..94143f777 100644 --- a/engine/classes/ElggFileCache.php +++ b/engine/classes/ElggFileCache.php @@ -13,6 +13,8 @@ class ElggFileCache extends ElggCache { * @param string $cache_path The cache path. * @param int $max_age Maximum age in seconds, 0 if no limit. * @param int $max_size Maximum size of cache in seconds, 0 if no limit. + * + * @throws ConfigurationException */ function __construct($cache_path, $max_age = 0, $max_size = 0) { $this->setVariable("cache_path", $cache_path); @@ -24,6 +26,7 @@ class ElggFileCache extends ElggCache { } } + // @codingStandardsIgnoreStart /** * Create and return a handle to a file. * @@ -39,6 +42,7 @@ class ElggFileCache extends ElggCache { return $this->createFile($filename, $rw); } + // @codingStandardsIgnoreEnd /** * Create and return a handle to a file. @@ -70,6 +74,7 @@ class ElggFileCache extends ElggCache { return fopen($path . $filename, $rw); } + // @codingStandardsIgnoreStart /** * Create a sanitised filename for the file. * @@ -84,6 +89,7 @@ class ElggFileCache extends ElggCache { return $filename; } + // @codingStandardsIgnoreEnd /** * Create a sanitised filename for the file. @@ -161,12 +167,25 @@ class ElggFileCache extends ElggCache { } /** - * This was probably meant to delete everything? + * Delete all files in the directory of this file cache * * @return void */ public function clear() { - // @todo writeme + $dir = $this->getVariable("cache_path"); + + $exclude = array(".", ".."); + + $files = scandir($dir); + if (!$files) { + return; + } + + foreach ($files as $f) { + if (!in_array($f, $exclude)) { + unlink($dir . $f); + } + } } /** @@ -184,11 +203,11 @@ class ElggFileCache extends ElggCache { return; } - $exclude = array(".",".."); + $exclude = array(".", ".."); $files = scandir($dir); if (!$files) { - throw new IOException(sprintf(elgg_echo('IOException:NotDirectory'), $dir)); + throw new IOException(elgg_echo('IOException:NotDirectory', array($dir))); } // Perform cleanup @@ -208,4 +227,4 @@ class ElggFileCache extends ElggCache { } } } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggFilestore.php b/engine/classes/ElggFilestore.php index 61ce167d0..16430feac 100644 --- a/engine/classes/ElggFilestore.php +++ b/engine/classes/ElggFilestore.php @@ -136,4 +136,4 @@ abstract class ElggFilestore { * @return bool */ abstract public function exists(ElggFile $file); -}
\ No newline at end of file +} diff --git a/engine/classes/ElggGroup.php b/engine/classes/ElggGroup.php index 00502dd39..7e69b7a84 100644 --- a/engine/classes/ElggGroup.php +++ b/engine/classes/ElggGroup.php @@ -5,75 +5,63 @@ * * @package Elgg.Core * @subpackage Groups + * + * @property string $name A short name that captures the purpose of the group + * @property string $description A longer body of content that gives more details about the group */ class ElggGroup extends ElggEntity implements Friendable { - - /** - * Sets the type to group. - * - * @return void - * - * @see ElggEntity::initialise_attributes() - */ - protected function initialise_attributes() { - elgg_deprecated_notice('ElggGroup::initialise_attributes() is deprecated by ::initializeAttributes()', 1.8); - return $this->initializeAttributes(); - } - /** * Sets the type to group. * * @return void - * - * @see ElggEntity::initialise_attributes() */ protected function initializeAttributes() { parent::initializeAttributes(); $this->attributes['type'] = "group"; - $this->attributes['name'] = ""; - $this->attributes['description'] = ""; + $this->attributes['name'] = NULL; + $this->attributes['description'] = NULL; $this->attributes['tables_split'] = 2; } /** - * Construct a new user entity, optionally from a given id value. + * Construct a new group entity, optionally from a given guid value. * * @param mixed $guid If an int, load that GUID. - * If a db row then will attempt to load the rest of the data. + * If an entity table db row, then will load the rest of the data. * - * @throws Exception if there was a problem creating the user. + * @throws IOException|InvalidParameterException if there was a problem creating the group. */ function __construct($guid = null) { $this->initializeAttributes(); + // compatibility for 1.7 api. + $this->initialise_attributes(false); + if (!empty($guid)) { - // Is $guid is a DB row - either a entity row, or a user table row. + // Is $guid is a entity table DB row if ($guid instanceof stdClass) { // Load the rest - if (!$this->load($guid->guid)) { - $msg = sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid); + if (!$this->load($guid)) { + $msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid)); throw new IOException($msg); } - - // Is $guid is an ElggGroup? Use a copy constructor } else if ($guid instanceof ElggGroup) { + // $guid is an ElggGroup so this is a copy constructor elgg_deprecated_notice('This type of usage of the ElggGroup constructor was deprecated. Please use the clone method.', 1.7); foreach ($guid->attributes as $key => $value) { $this->attributes[$key] = $value; } - - // Is this is an ElggEntity but not an ElggGroup = ERROR! } else if ($guid instanceof ElggEntity) { + // @todo why separate from else throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggGroup')); - - // We assume if we have got this far, $guid is an int } else if (is_numeric($guid)) { + // $guid is a GUID so load entity if (!$this->load($guid)) { - throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid)); + throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid))); } } else { throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); @@ -229,6 +217,7 @@ class ElggGroup extends ElggEntity * @return array|false */ public function getObjects($subtype = "", $limit = 10, $offset = 0) { + // @todo are we deprecating this method, too? return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", $limit, $offset, false); } @@ -242,6 +231,7 @@ class ElggGroup extends ElggEntity * @return array|false */ public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0) { + // @todo are we deprecating this method, too? return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", $limit, $offset, false); } @@ -253,6 +243,7 @@ class ElggGroup extends ElggEntity * @return array|false */ public function countObjects($subtype = "") { + // @todo are we deprecating this method, too? return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", 10, 0, true); } @@ -293,9 +284,9 @@ class ElggGroup extends ElggEntity * * @return bool */ - public function isMember($user = 0) { + public function isMember($user = null) { if (!($user instanceof ElggUser)) { - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); } if (!($user instanceof ElggUser)) { return false; @@ -319,45 +310,32 @@ class ElggGroup extends ElggEntity * * @param ElggUser $user User * - * @return void + * @return bool */ public function leave(ElggUser $user) { return leave_group($this->getGUID(), $user->getGUID()); } /** - * Override the load function. - * This function will ensure that all data is loaded (were possible), so - * if only part of the ElggGroup is loaded, it'll load the rest. + * Load the ElggGroup data from the database * - * @param int $guid GUID of an ElggGroup entity + * @param mixed $guid GUID of an ElggGroup entity or database row from entity table * - * @return true + * @return bool */ protected function load($guid) { - // Test to see if we have the generic stuff - if (!parent::load($guid)) { + $attr_loader = new ElggAttributeLoader(get_class(), 'group', $this->attributes); + $attr_loader->requires_access_control = !($this instanceof ElggPlugin); + $attr_loader->secondary_loader = 'get_group_entity_as_row'; + + $attrs = $attr_loader->getRequiredAttributes($guid); + if (!$attrs) { return false; } - // Check the type - if ($this->attributes['type'] != 'group') { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class()); - throw new InvalidClassException($msg); - } - - // Load missing data - $row = get_group_entity_as_row($guid); - if (($row) && (!$this->isFullyLoaded())) { - // If $row isn't a cached copy then increment the counter - $this->attributes['tables_loaded'] ++; - } - - // Now put these into the attributes array as core values - $objarray = (array) $row; - foreach ($objarray as $key => $value) { - $this->attributes[$key] = $value; - } + $this->attributes = $attrs; + $this->attributes['tables_loaded'] = 2; + _elgg_cache_entity($this); return true; } @@ -374,7 +352,12 @@ class ElggGroup extends ElggEntity } // Now save specific stuff - return create_group_entity($this->get('guid'), $this->get('name'), $this->get('description')); + + _elgg_disable_caching_for_entity($this->guid); + $ret = create_group_entity($this->get('guid'), $this->get('name'), $this->get('description')); + _elgg_enable_caching_for_entity($this->guid); + + return $ret; } // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// @@ -390,4 +373,21 @@ class ElggGroup extends ElggEntity 'description', )); } -}
\ No newline at end of file + + /** + * Can a user comment on this group? + * + * @see ElggEntity::canComment() + * + * @param int $user_guid User guid (default is logged in user) + * @return bool + * @since 1.8.0 + */ + public function canComment($user_guid = 0) { + $result = parent::canComment($user_guid); + if ($result !== null) { + return $result; + } + return false; + } +} diff --git a/engine/classes/ElggGroupItemVisibility.php b/engine/classes/ElggGroupItemVisibility.php new file mode 100644 index 000000000..2c7e2abb4 --- /dev/null +++ b/engine/classes/ElggGroupItemVisibility.php @@ -0,0 +1,93 @@ +<?php + +/** + * Determines if otherwise visible items should be hidden from a user due to group + * policy or visibility. + * + * @class ElggGroupItemVisibility + * @package Elgg.Core + * @subpackage Groups + * + * @access private + */ +class ElggGroupItemVisibility { + + const REASON_MEMBERSHIP = 'membershiprequired'; + const REASON_LOGGEDOUT = 'loggedinrequired'; + const REASON_NOACCESS = 'noaccess'; + + /** + * @var bool + */ + public $shouldHideItems = false; + + /** + * @var string + */ + public $reasonHidden = ''; + + /** + * Determine visibility of items within a container for the current user + * + * @param int $container_guid GUID of a container (may/may not be a group) + * + * @return ElggGroupItemVisibility + * + * @todo Make this faster, considering it must run for every river item. + */ + static public function factory($container_guid) { + // cache because this may be called repeatedly during river display, and + // due to need to check group visibility, cache will be disabled for some + // get_entity() calls + static $cache = array(); + + $ret = new ElggGroupItemVisibility(); + + if (!$container_guid) { + return $ret; + } + + $user = elgg_get_logged_in_user_entity(); + $user_guid = $user ? $user->guid : 0; + + $container_guid = (int) $container_guid; + + $cache_key = "$container_guid|$user_guid"; + if (empty($cache[$cache_key])) { + // compute + + $container = get_entity($container_guid); + $is_visible = (bool) $container; + + if (!$is_visible) { + // see if it *really* exists... + $prev_access = elgg_set_ignore_access(); + $container = get_entity($container_guid); + elgg_set_ignore_access($prev_access); + } + + if ($container && $container instanceof ElggGroup) { + /* @var ElggGroup $container */ + + if ($is_visible) { + if (!$container->isPublicMembership()) { + if ($user) { + if (!$container->isMember($user) && !$user->isAdmin()) { + $ret->shouldHideItems = true; + $ret->reasonHidden = self::REASON_MEMBERSHIP; + } + } else { + $ret->shouldHideItems = true; + $ret->reasonHidden = self::REASON_LOGGEDOUT; + } + } + } else { + $ret->shouldHideItems = true; + $ret->reasonHidden = self::REASON_NOACCESS; + } + } + $cache[$cache_key] = $ret; + } + return $cache[$cache_key]; + } +} diff --git a/engine/classes/ElggHMACCache.php b/engine/classes/ElggHMACCache.php index 8d994d013..c2f468815 100644 --- a/engine/classes/ElggHMACCache.php +++ b/engine/classes/ElggHMACCache.php @@ -96,4 +96,4 @@ class ElggHMACCache extends ElggCache { delete_data("DELETE from {$CONFIG->dbprefix}hmac_cache where ts<$expires"); } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggLRUCache.php b/engine/classes/ElggLRUCache.php new file mode 100644 index 000000000..f51af2ed7 --- /dev/null +++ b/engine/classes/ElggLRUCache.php @@ -0,0 +1,181 @@ +<?php + +/** + * Least Recently Used Cache + * + * A fixed sized cache that removes the element used last when it reaches its + * size limit. + * + * Based on https://github.com/cash/LRUCache + * + * @access private + * + * @package Elgg.Core + * @subpackage Cache + */ +class ElggLRUCache implements ArrayAccess { + /** @var int */ + protected $maximumSize; + + /** + * The front of the array contains the LRU element + * + * @var array + */ + protected $data = array(); + + /** + * Create a LRU Cache + * + * @param int $size The size of the cache + * @throws InvalidArgumentException + */ + public function __construct($size) { + if (!is_int($size) || $size <= 0) { + throw new InvalidArgumentException(); + } + $this->maximumSize = $size; + } + + /** + * Get the value cached with this key + * + * @param int|string $key The key. Strings that are ints are cast to ints. + * @param mixed $default The value to be returned if key not found. (Optional) + * @return mixed + */ + public function get($key, $default = null) { + if (isset($this->data[$key])) { + $this->recordAccess($key); + return $this->data[$key]; + } else { + return $default; + } + } + + /** + * Add something to the cache + * + * @param int|string $key The key. Strings that are ints are cast to ints. + * @param mixed $value The value to cache + * @return void + */ + public function set($key, $value) { + if (isset($this->data[$key])) { + $this->data[$key] = $value; + $this->recordAccess($key); + } else { + $this->data[$key] = $value; + if ($this->size() > $this->maximumSize) { + // remove least recently used element (front of array) + reset($this->data); + unset($this->data[key($this->data)]); + } + } + } + + /** + * Get the number of elements in the cache + * + * @return int + */ + public function size() { + return count($this->data); + } + + /** + * Does the cache contain an element with this key + * + * @param int|string $key The key + * @return boolean + */ + public function containsKey($key) { + return isset($this->data[$key]); + } + + /** + * Remove the element with this key. + * + * @param int|string $key The key + * @return mixed Value or null if not set + */ + public function remove($key) { + if (isset($this->data[$key])) { + $value = $this->data[$key]; + unset($this->data[$key]); + return $value; + } else { + return null; + } + } + + /** + * Clear the cache + * + * @return void + */ + public function clear() { + $this->data = array(); + } + + /** + * Moves the element from current position to end of array + * + * @param int|string $key The key + * @return void + */ + protected function recordAccess($key) { + $value = $this->data[$key]; + unset($this->data[$key]); + $this->data[$key] = $value; + } + + /** + * Assigns a value for the specified key + * + * @see ArrayAccess::offsetSet() + * + * @param int|string $key The key to assign the value to. + * @param mixed $value The value to set. + * @return void + */ + public function offsetSet($key, $value) { + $this->set($key, $value); + } + + /** + * Get the value for specified key + * + * @see ArrayAccess::offsetGet() + * + * @param int|string $key The key to retrieve. + * @return mixed + */ + public function offsetGet($key) { + return $this->get($key); + } + + /** + * Unsets a key. + * + * @see ArrayAccess::offsetUnset() + * + * @param int|string $key The key to unset. + * @return void + */ + public function offsetUnset($key) { + $this->remove($key); + } + + /** + * Does key exist? + * + * @see ArrayAccess::offsetExists() + * + * @param int|string $key A key to check for. + * @return boolean + */ + public function offsetExists($key) { + return $this->containsKey($key); + } +} diff --git a/engine/classes/ElggMemcache.php b/engine/classes/ElggMemcache.php index e0d8e7bee..91d50ab89 100644 --- a/engine/classes/ElggMemcache.php +++ b/engine/classes/ElggMemcache.php @@ -32,6 +32,8 @@ class ElggMemcache extends ElggSharedMemoryCache { * * @param string $namespace The namespace for this cache to write to - * note, namespaces of the same name are shared! + * + * @throws ConfigurationException */ function __construct($namespace = 'default') { global $CONFIG; @@ -40,7 +42,7 @@ class ElggMemcache extends ElggSharedMemoryCache { // Do we have memcache? if (!class_exists('Memcache')) { - throw new ConfigurationException(elgg_echo('memcache:notinstalled')); + throw new ConfigurationException('PHP memcache module not installed, you must install php5-memcache'); } // Create memcache object @@ -48,7 +50,7 @@ class ElggMemcache extends ElggSharedMemoryCache { // Now add servers if (!$CONFIG->memcache_servers) { - throw new ConfigurationException(elgg_echo('memcache:noservers')); + throw new ConfigurationException('No memcache servers defined, please populate the $CONFIG->memcache_servers variable'); } if (is_callable(array($this->memcache, 'addServer'))) { @@ -85,10 +87,10 @@ class ElggMemcache extends ElggSharedMemoryCache { // Get version $this->version = $this->memcache->getVersion(); if (version_compare($this->version, ElggMemcache::$MINSERVERVERSION, '<')) { - $msg = sprintf(elgg_echo('memcache:versiontoolow'), - ElggMemcache::$MINSERVERVERSION, + $msg = vsprintf('Memcache needs at least version %s to run, you are running %s', + array(ElggMemcache::$MINSERVERVERSION, $this->version - ); + )); throw new ConfigurationException($msg); } @@ -114,27 +116,11 @@ class ElggMemcache extends ElggSharedMemoryCache { * Combine a key with the namespace. * Memcache can only accept <250 char key. If the given key is too long it is shortened. * - * @deprecated 1.8 Use ElggMemcache::_makeMemcacheKey() - * * @param string $key The key * * @return string The new key. */ - private function make_memcache_key($key) { - elgg_deprecated_notice('ElggMemcache::make_memcache_key() is deprecated by ::_makeMemcacheKey()', 1.8); - - return $this->_makeMemcacheKey($key); - } - - /** - * Combine a key with the namespace. - * Memcache can only accept <250 char key. If the given key is too long it is shortened. - * - * @param string $key The key - * - * @return string The new key. - */ - private function _makeMemcacheKey($key) { + private function makeMemcacheKey($key) { $prefix = $this->getNamespace() . ":"; if (strlen($prefix . $key) > 250) { @@ -147,16 +133,21 @@ class ElggMemcache extends ElggSharedMemoryCache { /** * Saves a name and value to the cache * - * @param string $key Name - * @param string $data Value + * @param string $key Name + * @param string $data Value + * @param integer $expires Expires (in seconds) * * @return bool */ - public function save($key, $data) { - $key = $this->_makeMemcacheKey($key); + public function save($key, $data, $expires = null) { + $key = $this->makeMemcacheKey($key); + + if ($expires === null) { + $expires = $this->expires; + } - $result = $this->memcache->set($key, $data, null, $this->expires); - if (!$result) { + $result = $this->memcache->set($key, $data, null, $expires); + if ($result === false) { elgg_log("MEMCACHE: FAILED TO SAVE $key", 'ERROR'); } @@ -173,10 +164,10 @@ class ElggMemcache extends ElggSharedMemoryCache { * @return mixed */ public function load($key, $offset = 0, $limit = null) { - $key = $this->_makeMemcacheKey($key); + $key = $this->makeMemcacheKey($key); $result = $this->memcache->get($key); - if (!$result) { + if ($result === false) { elgg_log("MEMCACHE: FAILED TO LOAD $key", 'ERROR'); } @@ -191,7 +182,7 @@ class ElggMemcache extends ElggSharedMemoryCache { * @return bool */ public function delete($key) { - $key = $this->_makeMemcacheKey($key); + $key = $this->makeMemcacheKey($key); return $this->memcache->delete($key, 0); } diff --git a/engine/classes/ElggMenuBuilder.php b/engine/classes/ElggMenuBuilder.php new file mode 100644 index 000000000..b463143d8 --- /dev/null +++ b/engine/classes/ElggMenuBuilder.php @@ -0,0 +1,291 @@ +<?php +/** + * Elgg Menu Builder + * + * @package Elgg.Core + * @subpackage Navigation + * @since 1.8.0 + */ +class ElggMenuBuilder { + + /** + * @var ElggMenuItem[] + */ + protected $menu = array(); + + protected $selected = null; + + /** + * ElggMenuBuilder constructor + * + * @param ElggMenuItem[] $menu Array of ElggMenuItem objects + */ + public function __construct(array $menu) { + $this->menu = $menu; + } + + /** + * Get a prepared menu array + * + * @param mixed $sort_by Method to sort the menu by. @see ElggMenuBuilder::sort() + * @return array + */ + public function getMenu($sort_by = 'text') { + + $this->selectFromContext(); + + $this->selected = $this->findSelected(); + + $this->setupSections(); + + $this->setupTrees(); + + $this->sort($sort_by); + + return $this->menu; + } + + /** + * Get the selected menu item + * + * @return ElggMenuItem + */ + public function getSelected() { + return $this->selected; + } + + /** + * Select menu items for the current context + * + * @return void + */ + protected function selectFromContext() { + if (!isset($this->menu)) { + $this->menu = array(); + return; + } + + // get menu items for this context + $selected_menu = array(); + foreach ($this->menu as $menu_item) { + if (!is_object($menu_item)) { + elgg_log("A non-object was passed to ElggMenuBuilder", "ERROR"); + continue; + } + if ($menu_item->inContext()) { + $selected_menu[] = $menu_item; + } + } + + $this->menu = $selected_menu; + } + + /** + * Group the menu items into sections + * + * @return void + */ + protected function setupSections() { + $sectioned_menu = array(); + foreach ($this->menu as $menu_item) { + if (!isset($sectioned_menu[$menu_item->getSection()])) { + $sectioned_menu[$menu_item->getSection()] = array(); + } + $sectioned_menu[$menu_item->getSection()][] = $menu_item; + } + $this->menu = $sectioned_menu; + } + + /** + * Create trees for each menu section + * + * @internal The tree is doubly linked (parent and children links) + * @return void + */ + protected function setupTrees() { + $menu_tree = array(); + + foreach ($this->menu as $key => $section) { + $parents = array(); + $children = array(); + // divide base nodes from children + foreach ($section as $menu_item) { + /* @var ElggMenuItem $menu_item */ + $parent_name = $menu_item->getParentName(); + if (!$parent_name) { + $parents[$menu_item->getName()] = $menu_item; + } else { + $children[] = $menu_item; + } + } + + // attach children to parents + $iteration = 0; + $current_gen = $parents; + $next_gen = null; + while (count($children) && $iteration < 5) { + foreach ($children as $index => $menu_item) { + $parent_name = $menu_item->getParentName(); + if (array_key_exists($parent_name, $current_gen)) { + $next_gen[$menu_item->getName()] = $menu_item; + if (!in_array($menu_item, $current_gen[$parent_name]->getData('children'))) { + $current_gen[$parent_name]->addChild($menu_item); + $menu_item->setParent($current_gen[$parent_name]); + } + unset($children[$index]); + } + } + $current_gen = $next_gen; + $iteration += 1; + } + + // convert keys to indexes for first level of tree + $parents = array_values($parents); + + $menu_tree[$key] = $parents; + } + + $this->menu = $menu_tree; + } + + /** + * Find the menu item that is currently selected + * + * @return ElggMenuItem + */ + protected function findSelected() { + + // do we have a selected menu item already + foreach ($this->menu as $menu_item) { + if ($menu_item->getSelected()) { + return $menu_item; + } + } + + // scan looking for a selected item + foreach ($this->menu as $menu_item) { + if ($menu_item->getHref()) { + if (elgg_http_url_is_identical(current_page_url(), $menu_item->getHref())) { + $menu_item->setSelected(true); + return $menu_item; + } + } + } + + return null; + } + + /** + * Sort the menu sections and trees + * + * @param mixed $sort_by Sort type as string or php callback + * @return void + */ + protected function sort($sort_by) { + + // sort sections + ksort($this->menu); + + switch ($sort_by) { + case 'text': + $sort_callback = array('ElggMenuBuilder', 'compareByText'); + break; + case 'name': + $sort_callback = array('ElggMenuBuilder', 'compareByName'); + break; + case 'priority': + $sort_callback = array('ElggMenuBuilder', 'compareByWeight'); + break; + case 'register': + // use registration order - usort breaks this + return; + break; + default: + if (is_callable($sort_by)) { + $sort_callback = $sort_by; + } else { + return; + } + break; + } + + // sort each section + foreach ($this->menu as $index => $section) { + foreach ($section as $key => $node) { + $section[$key]->setData('original_order', $key); + } + usort($section, $sort_callback); + $this->menu[$index] = $section; + + // depth first traversal of tree + foreach ($section as $root) { + $stack = array(); + array_push($stack, $root); + while (!empty($stack)) { + $node = array_pop($stack); + /* @var ElggMenuItem $node */ + $node->sortChildren($sort_callback); + $children = $node->getChildren(); + if ($children) { + $stack = array_merge($stack, $children); + } + } + } + } + } + + /** + * Compare two menu items by their display text + * + * @param ElggMenuItem $a Menu item + * @param ElggMenuItem $b Menu item + * @return bool + */ + public static function compareByText($a, $b) { + $at = $a->getText(); + $bt = $b->getText(); + + $result = strnatcmp($at, $bt); + if ($result === 0) { + return $a->getData('original_order') - $b->getData('original_order'); + } + return $result; + } + + /** + * Compare two menu items by their identifiers + * + * @param ElggMenuItem $a Menu item + * @param ElggMenuItem $b Menu item + * @return bool + */ + public static function compareByName($a, $b) { + $an = $a->getName(); + $bn = $b->getName(); + + $result = strcmp($an, $bn); + if ($result === 0) { + return $a->getData('original_order') - $b->getData('original_order'); + } + return $result; + } + + /** + * Compare two menu items by their priority + * + * @param ElggMenuItem $a Menu item + * @param ElggMenuItem $b Menu item + * @return bool + * + * @todo change name to compareByPriority + */ + public static function compareByWeight($a, $b) { + $aw = $a->getWeight(); + $bw = $b->getWeight(); + + if ($aw == $bw) { + return $a->getData('original_order') - $b->getData('original_order'); + } + return $aw - $bw; + } +} diff --git a/engine/classes/ElggMenuItem.php b/engine/classes/ElggMenuItem.php new file mode 100644 index 000000000..81ce6c099 --- /dev/null +++ b/engine/classes/ElggMenuItem.php @@ -0,0 +1,590 @@ +<?php +/** + * Elgg Menu Item + * + * To create a menu item that is not a link, pass false for $href. + * + * @package Elgg.Core + * @subpackage Navigation + * @since 1.8.0 + */ +class ElggMenuItem { + + /** + * @var array Non-rendered data about the menu item + */ + protected $data = array( + // string Identifier of the menu + 'name' => '', + + // array Page contexts this menu item should appear on + 'contexts' => array('all'), + + // string Menu section identifier + 'section' => 'default', + + // int Smaller priorities float to the top + 'priority' => 100, + + // bool Is this the currently selected menu item + 'selected' => false, + + // string Identifier of this item's parent + 'parent_name' => '', + + // ElggMenuItem The parent object or null + 'parent' => null, + + // array Array of children objects or empty array + 'children' => array(), + + // array Classes to apply to the li tag + 'itemClass' => array(), + + // array Classes to apply to the anchor tag + 'linkClass' => array(), + ); + + /** + * @var string The menu display string + */ + protected $text; + + /** + * @var string The menu url + */ + protected $href = null; + + /** + * @var string Tooltip + */ + protected $title = false; + + /** + * @var string The string to display if link is clicked + */ + protected $confirm = ''; + + + /** + * ElggMenuItem constructor + * + * @param string $name Identifier of the menu item + * @param string $text Display text of the menu item + * @param string $href URL of the menu item (false if not a link) + */ + public function __construct($name, $text, $href) { + //$this->name = $name; + $this->text = $text; + if ($href) { + $this->href = elgg_normalize_url($href); + } else { + $this->href = $href; + } + + $this->data['name'] = $name; + } + + /** + * ElggMenuItem factory method + * + * This static method creates an ElggMenuItem from an associative array. + * Required keys are name, text, and href. + * + * @param array $options Option array of key value pairs + * + * @return ElggMenuItem or NULL on error + */ + public static function factory($options) { + if (!isset($options['name']) || !isset($options['text'])) { + return NULL; + } + if (!isset($options['href'])) { + $options['href'] = ''; + } + + $item = new ElggMenuItem($options['name'], $options['text'], $options['href']); + unset($options['name']); + unset($options['text']); + unset($options['href']); + + // special catch in case someone uses context rather than contexts + if (isset($options['context'])) { + $options['contexts'] = $options['context']; + unset($options['context']); + } + + // make sure contexts is set correctly + if (isset($options['contexts'])) { + $item->setContext($options['contexts']); + unset($options['contexts']); + } + + if (isset($options['link_class'])) { + $item->setLinkClass($options['link_class']); + unset($options['link_class']); + } + + if (isset($options['item_class'])) { + $item->setItemClass($options['item_class']); + unset($options['item_class']); + } + + if (isset($options['data']) && is_array($options['data'])) { + $item->setData($options['data']); + unset($options['data']); + } + + foreach ($options as $key => $value) { + if (isset($item->data[$key])) { + $item->data[$key] = $value; + } else { + $item->$key = $value; + } + } + + return $item; + } + + /** + * Set a data key/value pair or a set of key/value pairs + * + * This method allows storage of arbitrary data with this menu item. The + * data can be used for sorting, custom rendering, or any other use. + * + * @param mixed $key String key or an associative array of key/value pairs + * @param mixed $value The value if $key is a string + * @return void + */ + public function setData($key, $value = null) { + if (is_array($key)) { + $this->data += $key; + } else { + $this->data[$key] = $value; + } + } + + /** + * Get stored data + * + * @param string $key The key for the requested key/value pair + * @return mixed + */ + public function getData($key) { + if (isset($this->data[$key])) { + return $this->data[$key]; + } else { + return null; + } + } + + /** + * Set the identifier of the menu item + * + * @param string $name Unique identifier + * @return void + */ + public function setName($name) { + $this->data['name'] = $name; + } + + /** + * Get the identifier of the menu item + * + * @return string + */ + public function getName() { + return $this->data['name']; + } + + /** + * Set the display text of the menu item + * + * @param string $text The display text + * @return void + */ + public function setText($text) { + $this->text = $text; + } + + /** + * Get the display text of the menu item + * + * @return string + */ + public function getText() { + return $this->text; + } + + /** + * Set the URL of the menu item + * + * @param string $href URL or false if not a link + * @return void + */ + public function setHref($href) { + $this->href = $href; + } + + /** + * Get the URL of the menu item + * + * @return string + */ + public function getHref() { + return $this->href; + } + + /** + * Set the contexts that this menu item is available for + * + * @param array $contexts An array of context strings + * @return void + */ + public function setContext($contexts) { + if (is_string($contexts)) { + $contexts = array($contexts); + } + $this->data['contexts'] = $contexts; + } + + /** + * Get an array of context strings + * + * @return array + */ + public function getContext() { + return $this->data['contexts']; + } + + /** + * Should this menu item be used given the current context + * + * @param string $context A context string (default is empty string for + * current context stack). + * @return bool + */ + public function inContext($context = '') { + if ($context) { + return in_array($context, $this->data['contexts']); + } + + if (in_array('all', $this->data['contexts'])) { + return true; + } + + foreach ($this->data['contexts'] as $context) { + if (elgg_in_context($context)) { + return true; + } + } + return false; + } + + /** + * Set the selected flag + * + * @param bool $state Selected state (default is true) + * @return void + */ + public function setSelected($state = true) { + $this->data['selected'] = $state; + } + + /** + * Get selected state + * + * @return bool + */ + public function getSelected() { + return $this->data['selected']; + } + + /** + * Set the tool tip text + * + * @param string $text The text of the tool tip + * @return void + */ + public function setTooltip($text) { + $this->title = $text; + } + + /** + * Get the tool tip text + * + * @return string + */ + public function getTooltip() { + return $this->title; + } + + /** + * Set the confirm text shown when link is clicked + * + * @param string $text The text to show + * @return void + */ + public function setConfirmText($text) { + $this->confirm = $text; + } + + /** + * Get the confirm text + * + * @return string + */ + public function getConfirmText() { + return $this->confirm; + } + + /** + * Set the anchor class + * + * @param mixed $class An array of class names, or a single string class name. + * @return void + */ + public function setLinkClass($class) { + if (!is_array($class)) { + $this->data['linkClass'] = array($class); + } else { + $this->data['linkClass'] = $class; + } + } + + /** + * Get the anchor classes as text + * + * @return string + */ + public function getLinkClass() { + return implode(' ', $this->data['linkClass']); + } + + /** + * Add a link class + * + * @param mixed $class An array of class names, or a single string class name. + * @return void + */ + public function addLinkClass($class) { + if (!is_array($class)) { + $this->data['linkClass'][] = $class; + } else { + $this->data['linkClass'] += $class; + } + } + + /** + * Set the li classes + * + * @param mixed $class An array of class names, or a single string class name. + * @return void + */ + public function setItemClass($class) { + if (!is_array($class)) { + $this->data['itemClass'] = array($class); + } else { + $this->data['itemClass'] = $class; + } + } + + /** + * Get the li classes as text + * + * @return string + */ + public function getItemClass() { + // allow people to specify name with underscores and colons + $name = strtolower($this->getName()); + $name = str_replace('_', '-', $name); + $name = str_replace(':', '-', $name); + $name = str_replace(' ', '-', $name); + + $class = implode(' ', $this->data['itemClass']); + if ($class) { + return "elgg-menu-item-$name $class"; + } else { + return "elgg-menu-item-$name"; + } + } + + /** + * Set the priority of the menu item + * + * @param int $priority The smaller numbers mean higher priority (1 before 100) + * @return void + * @deprecated + */ + public function setWeight($priority) { + $this->data['priority'] = $priority; + } + + /** + * Get the priority of the menu item + * + * @return int + * @deprecated + */ + public function getWeight() { + return $this->data['priority']; + } + + /** + * Set the priority of the menu item + * + * @param int $priority The smaller numbers mean higher priority (1 before 100) + * @return void + */ + public function setPriority($priority) { + $this->data['priority'] = $priority; + } + + /** + * Get the priority of the menu item + * + * @return int + */ + public function getPriority() { + return $this->data['priority']; + } + + /** + * Set the section identifier + * + * @param string $section The identifier of the section + * @return void + */ + public function setSection($section) { + $this->data['section'] = $section; + } + + /** + * Get the section identifier + * + * @return string + */ + public function getSection() { + return $this->data['section']; + } + + /** + * Set the parent identifier + * + * @param string $name The identifier of the parent ElggMenuItem + * @return void + */ + public function setParentName($name) { + $this->data['parent_name'] = $name; + } + + /** + * Get the parent identifier + * + * @return string + */ + public function getParentName() { + return $this->data['parent_name']; + } + + /** + * Set the parent menu item + * + * @param ElggMenuItem $parent The parent of this menu item + * @return void + */ + public function setParent($parent) { + $this->data['parent'] = $parent; + } + + /** + * Get the parent menu item + * + * @return ElggMenuItem or null + */ + public function getParent() { + return $this->data['parent']; + } + + /** + * Add a child menu item + * + * @param ElggMenuItem $item A child menu item + * @return void + */ + public function addChild($item) { + $this->data['children'][] = $item; + } + + /** + * Set the menu item's children + * + * @param array $children Array of ElggMenuItems + * @return void + */ + public function setChildren($children) { + $this->data['children'] = $children; + } + + /** + * Get the children menu items + * + * @return array + */ + public function getChildren() { + return $this->data['children']; + } + + /** + * Sort the children + * + * @param string $sortFunction A function that is passed to usort() + * @return void + */ + public function sortChildren($sortFunction) { + foreach ($this->data['children'] as $key => $node) { + $this->data['children'][$key]->data['original_order'] = $key; + } + usort($this->data['children'], $sortFunction); + } + + /** + * Get the menu item content (usually a link) + * + * @param array $vars Options to pass to output/url if a link + * @return string + * @todo View code in a model. How do we feel about that? + */ + public function getContent(array $vars = array()) { + + if ($this->href === false) { + return $this->text; + } + + $defaults = get_object_vars($this); + unset($defaults['data']); + + $vars += $defaults; + + if ($this->data['linkClass']) { + if (isset($vars['class'])) { + $vars['class'] = $vars['class'] . ' ' . $this->getLinkClass(); + } else { + $vars['class'] = $this->getLinkClass(); + } + } + + if (!isset($vars['rel']) && !isset($vars['is_trusted'])) { + $vars['is_trusted'] = true; + } + + if ($this->confirm) { + $vars['confirm'] = $this->confirm; + return elgg_view('output/confirmlink', $vars); + } else { + unset($vars['confirm']); + } + + return elgg_view('output/url', $vars); + } +} diff --git a/engine/classes/ElggMetadata.php b/engine/classes/ElggMetadata.php index 90cbba5bd..3a8e2d817 100644 --- a/engine/classes/ElggMetadata.php +++ b/engine/classes/ElggMetadata.php @@ -6,21 +6,30 @@ * * @package Elgg.Core * @subpackage Metadata + * + * @property string $value_type + * @property int $owner_guid + * @property string $enabled */ class ElggMetadata extends ElggExtender { - + + /** + * (non-PHPdoc) + * + * @see ElggData::initializeAttributes() + * + * @return void + */ protected function initializeAttributes() { parent::initializeAttributes(); - + $this->attributes['type'] = "metadata"; } - + /** - * Construct a new site object, optionally from a given id value or row. - * - * @param mixed $id ID of metadata from DB + * Construct a metadata object * - * @return void + * @param mixed $id ID of metadata or a database row as stdClass object */ function __construct($id = null) { $this->initializeAttributes(); @@ -29,15 +38,15 @@ class ElggMetadata extends ElggExtender { // Create from db row if ($id instanceof stdClass) { $metadata = $id; - } else { - $metadata = get_metadata($id); - } - - if ($metadata) { + $objarray = (array) $metadata; foreach ($objarray as $key => $value) { $this->attributes[$key] = $value; } + } else { + // get an ElggMetadata object and copy its attributes + $metadata = elgg_get_metadata_from_id($id); + $this->attributes = $metadata->attributes; } } } @@ -45,19 +54,23 @@ class ElggMetadata extends ElggExtender { /** * Determines whether or not the user can edit this piece of metadata * - * @return true|false Depending on permissions + * @param int $user_guid The GUID of the user (defaults to currently logged in user) + * + * @return bool Depending on permissions */ - function canEdit() { + function canEdit($user_guid = 0) { if ($entity = get_entity($this->get('entity_guid'))) { - return $entity->canEditMetadata($this); + return $entity->canEditMetadata($this, $user_guid); } return false; } /** - * Save matadata object + * Save metadata object * - * @return int the metadata object id + * @return int|bool the metadata object id or true if updated + * + * @throws IOException */ function save() { if ($this->id > 0) { @@ -68,19 +81,55 @@ class ElggMetadata extends ElggExtender { $this->value_type, $this->owner_guid, $this->access_id); if (!$this->id) { - throw new IOException(sprintf(elgg_echo('IOException:UnableToSaveNew'), get_class())); + throw new IOException(elgg_echo('IOException:UnableToSaveNew', array(get_class()))); } return $this->id; } } /** - * Delete a given metadata. + * Delete the metadata * * @return bool */ function delete() { - return delete_metadata($this->id); + $success = elgg_delete_metastring_based_object_by_id($this->id, 'metadata'); + if ($success) { + // we mark unknown here because this deletes only one value + // under this name, and there may be others remaining. + elgg_get_metadata_cache()->markUnknown($this->entity_guid, $this->name); + } + return $success; + } + + /** + * Disable the metadata + * + * @return bool + * @since 1.8 + */ + function disable() { + $success = elgg_set_metastring_based_object_enabled_by_id($this->id, 'no', 'metadata'); + if ($success) { + // we mark unknown here because this disables only one value + // under this name, and there may be others remaining. + elgg_get_metadata_cache()->markUnknown($this->entity_guid, $this->name); + } + return $success; + } + + /** + * Enable the metadata + * + * @return bool + * @since 1.8 + */ + function enable() { + $success = elgg_set_metastring_based_object_enabled_by_id($this->id, 'yes', 'metadata'); + if ($success) { + elgg_get_metadata_cache()->markUnknown($this->entity_guid, $this->name); + } + return $success; } /** @@ -104,6 +153,6 @@ class ElggMetadata extends ElggExtender { * @return ElggMetadata */ public function getObjectFromID($id) { - return get_metadata($id); + return elgg_get_metadata_from_id($id); } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggObject.php b/engine/classes/ElggObject.php index b721c479c..aeaa3ba5c 100644 --- a/engine/classes/ElggObject.php +++ b/engine/classes/ElggObject.php @@ -14,21 +14,12 @@ * * @package Elgg.Core * @subpackage DataModel.Object + * + * @property string $title The title, name, or summary of this object + * @property string $description The body, description, or content of the object + * @property array $tags Array of tags that describe the object */ class ElggObject extends ElggEntity { - /** - * Initialise the attributes array to include the type, - * title, and description. - * - * @deprecated 1.8 use ElggEntity::initializeAttributes() - * - * @return void - */ - protected function initialise_attributes() { - elgg_deprecated_notice('ElggObject::initialise_attributes() is deprecated by ::initializeAttributes()', 1.8); - - return $this->initializeAttributes(); - } /** * Initialise the attributes array to include the type, @@ -40,8 +31,8 @@ class ElggObject extends ElggEntity { parent::initializeAttributes(); $this->attributes['type'] = "object"; - $this->attributes['title'] = ""; - $this->attributes['description'] = ""; + $this->attributes['title'] = NULL; + $this->attributes['description'] = NULL; $this->attributes['tables_split'] = 2; } @@ -50,12 +41,12 @@ class ElggObject extends ElggEntity { * * If no arguments are passed, create a new entity. * - * If an argument is passed attempt to load a full Object entity. Arguments - * can be: + * If an argument is passed, attempt to load a full ElggObject entity. + * Arguments can be: * - The GUID of an object entity. - * - A DB result object with a guid property + * - A DB result object from the entities table with a guid property * - * @param mixed $guid If an int, load that GUID. If a db row then will attempt to + * @param mixed $guid If an int, load that GUID. If a db row, then will attempt to * load the rest of the data. * * @throws IOException If passed an incorrect guid @@ -64,31 +55,31 @@ class ElggObject extends ElggEntity { function __construct($guid = null) { $this->initializeAttributes(); + // compatibility for 1.7 api. + $this->initialise_attributes(false); + if (!empty($guid)) { - // Is $guid is a DB row - either a entity row, or a object table row. + // Is $guid is a DB row from the entity table if ($guid instanceof stdClass) { // Load the rest - if (!$this->load($guid->guid)) { - $msg = sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid); + if (!$this->load($guid)) { + $msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid)); throw new IOException($msg); } - - // Is $guid is an ElggObject? Use a copy constructor } else if ($guid instanceof ElggObject) { + // $guid is an ElggObject so this is a copy constructor elgg_deprecated_notice('This type of usage of the ElggObject constructor was deprecated. Please use the clone method.', 1.7); foreach ($guid->attributes as $key => $value) { $this->attributes[$key] = $value; } - - // Is this is an ElggEntity but not an ElggObject = ERROR! } else if ($guid instanceof ElggEntity) { + // @todo remove - do not need separate exception throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggObject')); - - // We assume if we have got this far, $guid is an int } else if (is_numeric($guid)) { + // $guid is a GUID so load if (!$this->load($guid)) { - throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid)); + throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid))); } } else { throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); @@ -99,35 +90,24 @@ class ElggObject extends ElggEntity { /** * Loads the full ElggObject when given a guid. * - * @param int $guid Guid of an ElggObject + * @param mixed $guid GUID of an ElggObject or the stdClass object from entities table * * @return bool * @throws InvalidClassException */ protected function load($guid) { - // Test to see if we have the generic stuff - if (!parent::load($guid)) { - return false; - } - - // Check the type - if ($this->attributes['type'] != 'object') { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class()); - throw new InvalidClassException($msg); - } + $attr_loader = new ElggAttributeLoader(get_class(), 'object', $this->attributes); + $attr_loader->requires_access_control = !($this instanceof ElggPlugin); + $attr_loader->secondary_loader = 'get_object_entity_as_row'; - // Load missing data - $row = get_object_entity_as_row($guid); - if (($row) && (!$this->isFullyLoaded())) { - // If $row isn't a cached copy then increment the counter - $this->attributes['tables_loaded'] ++; + $attrs = $attr_loader->getRequiredAttributes($guid); + if (!$attrs) { + return false; } - // Now put these into the attributes array as core values - $objarray = (array) $row; - foreach ($objarray as $key => $value) { - $this->attributes[$key] = $value; - } + $this->attributes = $attrs; + $this->attributes['tables_loaded'] = 2; + _elgg_cache_entity($this); return true; } @@ -146,8 +126,12 @@ class ElggObject extends ElggEntity { } // Save ElggObject-specific attributes - return create_object_entity($this->get('guid'), $this->get('title'), - $this->get('description'), $this->get('container_guid')); + + _elgg_disable_caching_for_entity($this->guid); + $ret = create_object_entity($this->get('guid'), $this->get('title'), $this->get('description')); + _elgg_enable_caching_for_entity($this->guid); + + return $ret; } /** @@ -194,4 +178,39 @@ class ElggObject extends ElggEntity { 'description', )); } -}
\ No newline at end of file + + /** + * Can a user comment on this object? + * + * @see ElggEntity::canComment() + * + * @param int $user_guid User guid (default is logged in user) + * @return bool + * @since 1.8.0 + */ + public function canComment($user_guid = 0) { + $result = parent::canComment($user_guid); + if ($result !== null) { + return $result; + } + + if ($user_guid == 0) { + $user_guid = elgg_get_logged_in_user_guid(); + } + + // must be logged in to comment + if (!$user_guid) { + return false; + } + + // must be member of group + if (elgg_instanceof($this->getContainerEntity(), 'group')) { + if (!$this->getContainerEntity()->canWriteToContainer($user_guid)) { + return false; + } + } + + // no checks on read access since a user cannot see entities outside his access + return true; + } +} diff --git a/engine/classes/ElggPAM.php b/engine/classes/ElggPAM.php new file mode 100644 index 000000000..f07095fc1 --- /dev/null +++ b/engine/classes/ElggPAM.php @@ -0,0 +1,105 @@ +<?php +/** + * ElggPAM Pluggable Authentication Module + * + * @package Elgg.Core + * @subpackage Authentication + */ +class ElggPAM { + /** + * @var string PAM policy type: user, api or plugin-defined policies + */ + protected $policy; + + /** + * @var array Failure mesages + */ + protected $messages; + + /** + * ElggPAM constructor + * + * @param string $policy PAM policy type: user, api, or plugin-defined policies + */ + public function __construct($policy) { + $this->policy = $policy; + $this->messages = array('sufficient' => array(), 'required' => array()); + } + + /** + * Authenticate a set of credentials against a policy + * This function will process all registered PAM handlers or stop when the first + * handler fails. A handler fails by either returning false or throwing an + * exception. The advantage of throwing an exception is that it returns a message + * that can be passed to the user. The processing order of the handlers is + * determined by the order that they were registered. + * + * If $credentials are provided, the PAM handler should authenticate using the + * provided credentials. If not, then credentials should be prompted for or + * otherwise retrieved (eg from the HTTP header or $_SESSION). + * + * @param array $credentials Credentials array dependant on policy type + * @return bool + */ + public function authenticate($credentials = array()) { + global $_PAM_HANDLERS; + + if (!isset($_PAM_HANDLERS[$this->policy]) || + !is_array($_PAM_HANDLERS[$this->policy])) { + return false; + } + + $authenticated = false; + + foreach ($_PAM_HANDLERS[$this->policy] as $k => $v) { + $handler = $v->handler; + if (!is_callable($handler)) { + continue; + } + /* @var callable $handler */ + + $importance = $v->importance; + + try { + // Execute the handler + // @todo don't assume $handler is a global function + $result = call_user_func($handler, $credentials); + if ($result) { + $authenticated = true; + } elseif ($result === false) { + if ($importance == 'required') { + $this->messages['required'][] = "$handler:failed"; + return false; + } else { + $this->messages['sufficient'][] = "$handler:failed"; + } + } + } catch (Exception $e) { + if ($importance == 'required') { + $this->messages['required'][] = $e->getMessage(); + return false; + } else { + $this->messages['sufficient'][] = $e->getMessage(); + } + } + } + + return $authenticated; + } + + /** + * Get a failure message to display to user + * + * @return string + */ + public function getFailureMessage() { + $message = elgg_echo('auth:nopams'); + if (!empty($this->messages['required'])) { + $message = $this->messages['required'][0]; + } elseif (!empty($this->messages['sufficient'])) { + $message = $this->messages['sufficient'][0]; + } + + return elgg_trigger_plugin_hook('fail', 'auth', $this->messages, $message); + } +} diff --git a/engine/classes/ElggPlugin.php b/engine/classes/ElggPlugin.php index 0ea7fdf61..545b9a53c 100644 --- a/engine/classes/ElggPlugin.php +++ b/engine/classes/ElggPlugin.php @@ -9,29 +9,857 @@ * @subpackage Plugins.Settings */ class ElggPlugin extends ElggObject { + private $package; + private $manifest; + + private $path; + private $pluginID; + private $errorMsg = ''; /** * Set subtype to 'plugin' * * @return void */ - protected function initialise_attributes() { - elgg_deprecated_notice('ElggPlugin::initialise_attributes() is deprecated by ::initializeAttributes()', 1.8); - return $this->initializeAttributes(); + protected function initializeAttributes() { + parent::initializeAttributes(); + + $this->attributes['subtype'] = "plugin"; + + // plugins must be public. + $this->access_id = ACCESS_PUBLIC; } /** - * Set subtype to 'plugin' + * Loads the plugin by GUID or path. * - * @return void + * @warning Unlike other ElggEntity objects, you cannot null instantiate + * ElggPlugin. You must point it to an actual plugin GUID or location. + * + * @param mixed $plugin The GUID of the ElggPlugin object or the path of the plugin to load. + * + * @throws PluginException */ - protected function initializeAttributes() { - parent::initializeAttributes(); + public function __construct($plugin) { + if (!$plugin) { + throw new PluginException(elgg_echo('PluginException:NullInstantiated')); + } - $this->attributes['subtype'] = "plugin"; + // ElggEntity can be instantiated with a guid or an object. + // @todo plugins w/id 12345 + if (is_numeric($plugin) || is_object($plugin)) { + parent::__construct($plugin); + $this->path = elgg_get_plugins_path() . $this->getID(); + } else { + $plugin_path = elgg_get_plugins_path(); + + // not a full path, so assume an id + // use the default path + if (strpos($plugin, $plugin_path) !== 0) { + $plugin = $plugin_path . $plugin; + } + + // path checking is done in the package + $plugin = sanitise_filepath($plugin); + $this->path = $plugin; + $path_parts = explode('/', rtrim($plugin, '/')); + $plugin_id = array_pop($path_parts); + $this->pluginID = $plugin_id; + + // check if we're loading an existing plugin + $existing_plugin = elgg_get_plugin_from_id($this->pluginID); + $existing_guid = null; + + if ($existing_plugin) { + $existing_guid = $existing_plugin->guid; + } + + // load the rest of the plugin + parent::__construct($existing_guid); + } + + _elgg_cache_plugin_by_id($this); + } + + /** + * Save the plugin object. Make sure required values exist. + * + * @see ElggObject::save() + * @return bool + */ + public function save() { + // own by the current site so users can be deleted without affecting plugins + $site = get_config('site'); + $this->attributes['site_guid'] = $site->guid; + $this->attributes['owner_guid'] = $site->guid; + $this->attributes['container_guid'] = $site->guid; + $this->attributes['title'] = $this->pluginID; + + if (parent::save()) { + // make sure we have a priority + $priority = $this->getPriority(); + if ($priority === FALSE || $priority === NULL) { + return $this->setPriority('last'); + } + } else { + return false; + } + } + + + // Plugin ID and path + + /** + * Returns the ID (dir name) of this plugin + * + * @return string + */ + public function getID() { + return $this->title; + } + + /** + * Returns the manifest's name if available, otherwise the ID. + * + * @return string + * @since 1.8.1 + */ + public function getFriendlyName() { + $manifest = $this->getManifest(); + if ($manifest) { + return $manifest->getName(); + } + + return $this->getID(); + } + + /** + * Returns the plugin's full path with trailing slash. + * + * @return string + */ + public function getPath() { + return sanitise_filepath($this->path); + } + + /** + * Sets the location of this plugin. + * + * @param string $id The path to the plugin's dir. + * @return bool + */ + public function setID($id) { + return $this->attributes['title'] = $id; + } + + /** + * Returns an array of available markdown files for this plugin + * + * @return array + */ + public function getAvailableTextFiles() { + $filenames = $this->getPackage()->getTextFilenames(); + + $files = array(); + foreach ($filenames as $filename) { + if ($this->canReadFile($filename)) { + $files[$filename] = "$this->path/$filename"; + } + } + + return $files; + } + + // Load Priority + + /** + * Gets the plugin's load priority. + * + * @return int + */ + public function getPriority() { + $name = elgg_namespace_plugin_private_setting('internal', 'priority'); + return $this->$name; + } + + /** + * Sets the priority of the plugin + * + * @param mixed $priority The priority to set. One of +1, -1, first, last, or a number. + * If given a number, this will displace all plugins at that number + * and set their priorities +1 + * @param mixed $site_guid Optional site GUID. + * @return bool + */ + public function setPriority($priority, $site_guid = null) { + if (!$this->guid) { + return false; + } + + $db_prefix = get_config('dbprefix'); + $name = elgg_namespace_plugin_private_setting('internal', 'priority'); + // if no priority assume a priority of 1 + $old_priority = (int) $this->getPriority(); + $old_priority = (!$old_priority) ? 1 : $old_priority; + $max_priority = elgg_get_max_plugin_priority(); + + // can't use switch here because it's not strict and + // php evaluates +1 == 1 + if ($priority === '+1') { + $priority = $old_priority + 1; + } elseif ($priority === '-1') { + $priority = $old_priority - 1; + } elseif ($priority === 'first') { + $priority = 1; + } elseif ($priority === 'last') { + $priority = $max_priority; + } + + // should be a number by now + if ($priority > 0) { + if (!is_numeric($priority)) { + return false; + } + + // there's nothing above the max. + if ($priority > $max_priority) { + $priority = $max_priority; + } + + // there's nothing below 1. + if ($priority < 1) { + $priority = 1; + } + + if ($priority > $old_priority) { + $op = '-'; + $where = "CAST(value as unsigned) BETWEEN $old_priority AND $priority"; + } else { + $op = '+'; + $where = "CAST(value as unsigned) BETWEEN $priority AND $old_priority"; + } + + // displace the ones affected by this change + $q = "UPDATE {$db_prefix}private_settings + SET value = CAST(value as unsigned) $op 1 + WHERE entity_guid != $this->guid + AND name = '$name' + AND $where"; + + if (!update_data($q)) { + return false; + } + + // set this priority + if ($this->set($name, $priority)) { + return true; + } else { + return false; + } + } + + return false; + } + + + // Plugin settings + + /** + * Returns a plugin setting + * + * @param string $name The setting name + * @return mixed + */ + public function getSetting($name) { + return $this->$name; + } + + /** + * Returns an array of all settings saved for this plugin. + * + * @note Unlike user settings, plugin settings are not namespaced. + * + * @return array An array of key/value pairs. + */ + public function getAllSettings() { + if (!$this->guid) { + return false; + } + + $db_prefix = elgg_get_config('dbprefix'); + // need to remove all namespaced private settings. + $us_prefix = elgg_namespace_plugin_private_setting('user_setting', '', $this->getID()); + $is_prefix = elgg_namespace_plugin_private_setting('internal', '', $this->getID()); + + // Get private settings for user + $q = "SELECT * FROM {$db_prefix}private_settings + WHERE entity_guid = $this->guid + AND name NOT LIKE '$us_prefix%' + AND name NOT LIKE '$is_prefix%'"; + + $private_settings = get_data($q); + + $return = array(); + + if ($private_settings) { + foreach ($private_settings as $setting) { + $return[$setting->name] = $setting->value; + } + } + + return $return; + } + + /** + * Set a plugin setting for the plugin + * + * @todo This will only work once the plugin has a GUID. + * + * @param string $name The name to set + * @param string $value The value to set + * + * @return bool + */ + public function setSetting($name, $value) { + if (!$this->guid) { + return false; + } + + return $this->set($name, $value); + } + + /** + * Removes a plugin setting name and value. + * + * @param string $name The setting name to remove + * + * @return bool + */ + public function unsetSetting($name) { + return remove_private_setting($this->guid, $name); + } + + /** + * Removes all settings for this plugin. + * + * @todo Should be a better way to do this without dropping to raw SQL. + * @todo If we could namespace the plugin settings this would be cleaner. + * @return bool + */ + public function unsetAllSettings() { + $db_prefix = get_config('dbprefix'); + + $us_prefix = elgg_namespace_plugin_private_setting('user_setting', '', $this->getID()); + $is_prefix = elgg_namespace_plugin_private_setting('internal', '', $this->getID()); + + $q = "DELETE FROM {$db_prefix}private_settings + WHERE entity_guid = $this->guid + AND name NOT LIKE '$us_prefix%' + AND name NOT LIKE '$is_prefix%'"; + + return delete_data($q); + } + + + // User settings + + /** + * Returns a user's setting for this plugin + * + * @param string $name The setting name + * @param int $user_guid The user GUID + * + * @return mixed The setting string value or false + */ + public function getUserSetting($name, $user_guid = null) { + $user_guid = (int)$user_guid; + + if ($user_guid) { + $user = get_entity($user_guid); + } else { + $user = elgg_get_logged_in_user_entity(); + } + + if (!($user instanceof ElggUser)) { + return false; + } + + $name = elgg_namespace_plugin_private_setting('user_setting', $name, $this->getID()); + return get_private_setting($user->guid, $name); } /** + * Returns an array of all user settings saved for this plugin for the user. + * + * @note Plugin settings are saved with a prefix. This removes that prefix. + * + * @param int $user_guid The user GUID. Defaults to logged in. + * @return array An array of key/value pairs. + */ + public function getAllUserSettings($user_guid = null) { + $user_guid = (int)$user_guid; + + if ($user_guid) { + $user = get_entity($user_guid); + } else { + $user = elgg_get_logged_in_user_entity(); + } + + if (!($user instanceof ElggUser)) { + return false; + } + + $db_prefix = elgg_get_config('dbprefix'); + // send an empty name so we just get the first part of the namespace + $ps_prefix = elgg_namespace_plugin_private_setting('user_setting', '', $this->getID()); + $ps_prefix_len = strlen($ps_prefix); + + // Get private settings for user + $q = "SELECT * FROM {$db_prefix}private_settings + WHERE entity_guid = {$user->guid} + AND name LIKE '$ps_prefix%'"; + + $private_settings = get_data($q); + + $return = array(); + + if ($private_settings) { + foreach ($private_settings as $setting) { + $name = substr($setting->name, $ps_prefix_len); + $value = $setting->value; + + $return[$name] = $value; + } + } + + return $return; + } + + /** + * Sets a user setting for a plugin + * + * @param string $name The setting name + * @param string $value The setting value + * @param int $user_guid The user GUID + * + * @return mixed The new setting ID or false + */ + public function setUserSetting($name, $value, $user_guid = null) { + $user_guid = (int)$user_guid; + + if ($user_guid) { + $user = get_entity($user_guid); + } else { + $user = elgg_get_logged_in_user_entity(); + } + + if (!($user instanceof ElggUser)) { + return false; + } + + // Hook to validate setting + // note: this doesn't pass the namespaced name + $value = elgg_trigger_plugin_hook('usersetting', 'plugin', array( + 'user' => $user, + 'plugin' => $this, + 'plugin_id' => $this->getID(), + 'name' => $name, + 'value' => $value + ), $value); + + // set the namespaced name. + $name = elgg_namespace_plugin_private_setting('user_setting', $name, $this->getID()); + + return set_private_setting($user->guid, $name, $value); + } + + + /** + * Removes a user setting name and value. + * + * @param string $name The user setting name + * @param int $user_guid The user GUID + * @return bool + */ + public function unsetUserSetting($name, $user_guid = null) { + $user_guid = (int)$user_guid; + + if ($user_guid) { + $user = get_entity($user_guid); + } else { + $user = elgg_get_logged_in_user_entity(); + } + + if (!($user instanceof ElggUser)) { + return false; + } + + // set the namespaced name. + $name = elgg_namespace_plugin_private_setting('user_setting', $name, $this->getID()); + + return remove_private_setting($user->guid, $name); + } + + /** + * Removes all User Settings for this plugin + * + * Use {@link removeAllUsersSettings()} to remove all user + * settings for all users. (Note the plural 'Users'.) + * + * @param int $user_guid The user GUID to remove user settings. + * @return bool + */ + public function unsetAllUserSettings($user_guid) { + $db_prefix = get_config('dbprefix'); + $ps_prefix = elgg_namespace_plugin_private_setting('user_setting', '', $this->getID()); + + $q = "DELETE FROM {$db_prefix}private_settings + WHERE entity_guid = $user_guid + AND name LIKE '$ps_prefix%'"; + + return delete_data($q); + } + + /** + * Removes this plugin's user settings for all users. + * + * Use {@link removeAllUserSettings()} if you just want to remove + * settings for a single user. + * + * @return bool + */ + public function unsetAllUsersSettings() { + $db_prefix = get_config('dbprefix'); + $ps_prefix = elgg_namespace_plugin_private_setting('user_setting', '', $this->getID()); + + $q = "DELETE FROM {$db_prefix}private_settings + WHERE name LIKE '$ps_prefix%'"; + + return delete_data($q); + } + + + // validation + + /** + * Returns if the plugin is complete, meaning has all required files + * and Elgg can read them and they make sense. + * + * @todo bad name? This could be confused with isValid() from ElggPluginPackage. + * + * @return bool + */ + public function isValid() { + if (!$this->getID()) { + $this->errorMsg = elgg_echo('ElggPlugin:NoId', array($this->guid)); + return false; + } + + if (!$this->getPackage() instanceof ElggPluginPackage) { + $this->errorMsg = elgg_echo('ElggPlugin:NoPluginPackagePackage', array($this->getID(), $this->guid)); + return false; + } + + if (!$this->getPackage()->isValid()) { + $this->errorMsg = $this->getPackage()->getError(); + return false; + } + + return true; + } + + /** + * Is this plugin active? + * + * @param int $site_guid Optional site guid. + * @return bool + */ + public function isActive($site_guid = null) { + if (!$this->guid) { + return false; + } + + if ($site_guid) { + $site = get_entity($site_guid); + } else { + $site = get_config('site'); + } + + if (!($site instanceof ElggSite)) { + return false; + } + + return check_entity_relationship($this->guid, 'active_plugin', $site->guid); + } + + /** + * Checks if this plugin can be activated on the current + * Elgg installation. + * + * @todo remove $site_guid param or implement it + * + * @param mixed $site_guid Optional site guid + * @return bool + */ + public function canActivate($site_guid = null) { + if ($this->getPackage()) { + $result = $this->getPackage()->isValid() && $this->getPackage()->checkDependencies(); + if (!$result) { + $this->errorMsg = $this->getPackage()->getError(); + } + + return $result; + } + + return false; + } + + + // activating and deactivating + + /** + * Actives the plugin for the current site. + * + * @param mixed $site_guid Optional site GUID. + * @return bool + */ + public function activate($site_guid = null) { + if ($this->isActive($site_guid)) { + return false; + } + + if (!$this->canActivate()) { + return false; + } + + // set in the db, now perform tasks and emit events + if ($this->setStatus(true, $site_guid)) { + // emit an event. returning false will make this not be activated. + // we need to do this after it's been fully activated + // or the deactivate will be confused. + $params = array( + 'plugin_id' => $this->pluginID, + 'plugin_entity' => $this + ); + + $return = elgg_trigger_event('activate', 'plugin', $params); + + // if there are any on_enable functions, start the plugin now and run them + // Note: this will not run re-run the init hooks! + if ($return) { + if ($this->canReadFile('activate.php')) { + $flags = ELGG_PLUGIN_INCLUDE_START | ELGG_PLUGIN_REGISTER_CLASSES | + ELGG_PLUGIN_REGISTER_LANGUAGES | ELGG_PLUGIN_REGISTER_VIEWS; + + $this->start($flags); + + $return = $this->includeFile('activate.php'); + } + } + + if ($return === false) { + $this->deactivate($site_guid); + } + + return $return; + } + + return false; + } + + /** + * Deactivates the plugin. + * + * @param mixed $site_guid Optional site GUID. + * @return bool + */ + public function deactivate($site_guid = null) { + if (!$this->isActive($site_guid)) { + return false; + } + + // emit an event. returning false will cause this to not be deactivated. + $params = array( + 'plugin_id' => $this->pluginID, + 'plugin_entity' => $this + ); + + $return = elgg_trigger_event('deactivate', 'plugin', $params); + + // run any deactivate code + if ($return) { + if ($this->canReadFile('deactivate.php')) { + $return = $this->includeFile('deactivate.php'); + } + } + + if ($return === false) { + return false; + } else { + return $this->setStatus(false, $site_guid); + } + } + + /** + * Start the plugin. + * + * @param int $flags Start flags for the plugin. See the constants in lib/plugins.php for details. + * @return true + * @throws PluginException + */ + public function start($flags) { + //if (!$this->canActivate()) { + // return false; + //} + + // include classes + if ($flags & ELGG_PLUGIN_REGISTER_CLASSES) { + $this->registerClasses(); + } + + // include start file + if ($flags & ELGG_PLUGIN_INCLUDE_START) { + $this->includeFile('start.php'); + } + + // include views + if ($flags & ELGG_PLUGIN_REGISTER_VIEWS) { + $this->registerViews(); + } + + // include languages + if ($flags & ELGG_PLUGIN_REGISTER_LANGUAGES) { + $this->registerLanguages(); + } + + return true; + } + + + // start helpers + + /** + * Includes one of the plugins files + * + * @param string $filename The name of the file + * + * @throws PluginException + * @return mixed The return value of the included file (or 1 if there is none) + */ + protected function includeFile($filename) { + // This needs to be here to be backwards compatible for 1.0-1.7. + // They expect the global config object to be available in start.php. + if ($filename == 'start.php') { + global $CONFIG; + } + + $filepath = "$this->path/$filename"; + + if (!$this->canReadFile($filename)) { + $msg = elgg_echo('ElggPlugin:Exception:CannotIncludeFile', + array($filename, $this->getID(), $this->guid, $this->path)); + throw new PluginException($msg); + } + + return include $filepath; + } + + /** + * Checks whether a plugin file with the given name exists + * + * @param string $filename The name of the file + * @return bool + */ + protected function canReadFile($filename) { + return is_readable($this->path . '/' . $filename); + } + + /** + * Registers the plugin's views + * + * @throws PluginException + * @return true + */ + protected function registerViews() { + $view_dir = "$this->path/views/"; + + // plugins don't have to have views. + if (!is_dir($view_dir)) { + return true; + } + + // but if they do, they have to be readable + $handle = opendir($view_dir); + if (!$handle) { + $msg = elgg_echo('ElggPlugin:Exception:CannotRegisterViews', + array($this->getID(), $this->guid, $view_dir)); + throw new PluginException($msg); + } + + while (FALSE !== ($view_type = readdir($handle))) { + $view_type_dir = $view_dir . $view_type; + + if ('.' !== substr($view_type, 0, 1) && is_dir($view_type_dir)) { + if (autoregister_views('', $view_type_dir, $view_dir, $view_type)) { + elgg_register_viewtype($view_type); + } else { + $msg = elgg_echo('ElggPlugin:Exception:CannotRegisterViews', + array($this->getID(), $view_type_dir)); + throw new PluginException($msg); + } + } + } + + return true; + } + + /** + * Registers the plugin's languages + * + * @throws PluginException + * @return true + */ + protected function registerLanguages() { + $languages_path = "$this->path/languages"; + + // don't need to have classes + if (!is_dir($languages_path)) { + return true; + } + + // but need to have working ones. + if (!register_translations($languages_path)) { + $msg = elgg_echo('ElggPlugin:Exception:CannotRegisterLanguages', + array($this->getID(), $this->guid, $languages_path)); + throw new PluginException($msg); + } + + return true; + } + + /** + * Registers the plugin's classes + * + * @throws PluginException + * @return true + */ + protected function registerClasses() { + $classes_path = "$this->path/classes"; + + // don't need to have classes + if (!is_dir($classes_path)) { + return true; + } + + elgg_register_classes($classes_path); + + return true; + } + + + // generic helpers and overrides + + /** * Get a value from private settings. * * @param string $name Name @@ -39,14 +867,22 @@ class ElggPlugin extends ElggObject { * @return mixed */ public function get($name) { + // rewrite for old and inaccurate plugin:setting + if (strstr($name, 'plugin:setting:')) { + $msg = 'Direct access of user settings is deprecated. Use ElggPlugin->getUserSetting()'; + elgg_deprecated_notice($msg, 1.8); + $name = str_replace('plugin:setting:', '', $name); + $name = elgg_namespace_plugin_private_setting('user_setting', $name); + } + // See if its in our base attribute - if (isset($this->attributes[$name])) { + if (array_key_exists($name, $this->attributes)) { return $this->attributes[$name]; } // No, so see if its in the private data store. // get_private_setting() returns false if it doesn't exist - $meta = get_private_setting($this->guid, $name); + $meta = $this->getPrivateSetting($name); if ($meta === false) { // Can't find it, so return null @@ -57,7 +893,9 @@ class ElggPlugin extends ElggObject { } /** - * Save a value to private settings. + * Save a value as private setting or attribute. + * + * Attributes include title and description. * * @param string $name Name * @param mixed $value Value @@ -72,10 +910,97 @@ class ElggPlugin extends ElggObject { } $this->attributes[$name] = $value; + + return true; } else { - return set_private_setting($this->guid, $name, $value); + // Hook to validate setting + $value = elgg_trigger_plugin_hook('setting', 'plugin', array( + 'plugin_id' => $this->pluginID, + 'plugin' => $this, + 'name' => $name, + 'value' => $value + ), $value); + + return $this->setPrivateSetting($name, $value); + } + } + + /** + * Sets the plugin to active or inactive for $site_guid. + * + * @param bool $active Set to active or inactive + * @param mixed $site_guid Int for specific site, null for current site. + * + * @return bool + */ + private function setStatus($active, $site_guid = null) { + if (!$this->guid) { + return false; } - return true; + if ($site_guid) { + $site = get_entity($site_guid); + + if (!($site instanceof ElggSite)) { + return false; + } + } else { + $site = get_config('site'); + } + + if ($active) { + return add_entity_relationship($this->guid, 'active_plugin', $site->guid); + } else { + return remove_entity_relationship($this->guid, 'active_plugin', $site->guid); + } + } + + /** + * Returns the last error message registered. + * + * @return string|null + */ + public function getError() { + return $this->errorMsg; + } + + /** + * Returns this plugin's ElggPluginManifest object + * + * @return ElggPluginManifest + */ + public function getManifest() { + if ($this->manifest instanceof ElggPluginManifest) { + return $this->manifest; + } + + try { + $this->manifest = $this->getPackage()->getManifest(); + } catch (Exception $e) { + elgg_log("Failed to load manifest for plugin $this->guid. " . $e->getMessage(), 'WARNING'); + $this->errorMsg = $e->getmessage(); + } + + return $this->manifest; + } + + /** + * Returns this plugin's ElggPluginPackage object + * + * @return ElggPluginPackage + */ + public function getPackage() { + if ($this->package instanceof ElggPluginPackage) { + return $this->package; + } + + try { + $this->package = new ElggPluginPackage($this->path, false); + } catch (Exception $e) { + elgg_log("Failed to load package for $this->guid. " . $e->getMessage(), 'WARNING'); + $this->errorMsg = $e->getmessage(); + } + + return $this->package; } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggPluginManifest.php b/engine/classes/ElggPluginManifest.php new file mode 100644 index 000000000..6912c2b08 --- /dev/null +++ b/engine/classes/ElggPluginManifest.php @@ -0,0 +1,656 @@ +<?php +/** + * Parses Elgg manifest.xml files. + * + * Normalizes the values from the ElggManifestParser object. + * + * This requires an ElggPluginManifestParser class implementation + * as $this->parser. + * + * To add new parser versions, name them ElggPluginManifestParserXX + * where XX is the version specified in the top-level <plugin_manifest> + * tag's XML namespace. + * + * @package Elgg.Core + * @subpackage Plugins + * @since 1.8 + */ +class ElggPluginManifest { + + /** + * The parser object + */ + protected $parser; + + /** + * The root for plugin manifest namespaces. + * This is in the format http://www.elgg.org/plugin_manifest/<version> + */ + protected $namespace_root = 'http://www.elgg.org/plugin_manifest/'; + + /** + * The expected structure of a plugins requires element + */ + private $depsStructPlugin = array( + 'type' => '', + 'name' => '', + 'version' => '', + 'comparison' => 'ge' + ); + + /** + * The expected structure of a priority element + */ + private $depsStructPriority = array( + 'type' => '', + 'priority' => '', + 'plugin' => '' + ); + + /* + * The expected structure of elgg_version and elgg_release requires element + */ + private $depsStructElgg = array( + 'type' => '', + 'version' => '', + 'comparison' => 'ge' + ); + + /** + * The expected structure of a requires php_ini dependency element + */ + private $depsStructPhpIni = array( + 'type' => '', + 'name' => '', + 'value' => '', + 'comparison' => '=' + ); + + /** + * The expected structure of a requires php_extension dependency element + */ + private $depsStructPhpExtension = array( + 'type' => '', + 'name' => '', + 'version' => '', + 'comparison' => '=' + ); + + /** + * The expected structure of a conflicts depedency element + */ + private $depsConflictsStruct = array( + 'type' => '', + 'name' => '', + 'version' => '', + 'comparison' => '=' + ); + + /** + * The expected structure of a provides dependency element. + */ + private $depsProvidesStruct = array( + 'type' => '', + 'name' => '', + 'version' => '' + ); + + /** + * The expected structure of a screenshot element + */ + private $screenshotStruct = array( + 'description' => '', + 'path' => '' + ); + + /** + * The API version of the manifest. + * + * @var int + */ + protected $apiVersion; + + /** + * The optional plugin id this manifest belongs to. + * + * @var string + */ + protected $pluginID; + + /** + * Load a manifest file, XmlElement or path to manifest.xml file + * + * @param mixed $manifest A string, XmlElement, or path of a manifest file. + * @param string $plugin_id Optional ID of the owning plugin. Used to + * fill in some values automatically. + */ + public function __construct($manifest, $plugin_id = null) { + if ($plugin_id) { + $this->pluginID = $plugin_id; + } + + // see if we need to construct the xml object. + if ($manifest instanceof ElggXMLElement) { + $manifest_obj = $manifest; + } else { + if (substr(trim($manifest), 0, 1) == '<') { + // this is a string + $raw_xml = $manifest; + } elseif (is_file($manifest)) { + // this is a file + $raw_xml = file_get_contents($manifest); + } + + $manifest_obj = xml_to_object($raw_xml); + } + + if (!$manifest_obj) { + throw new PluginException(elgg_echo('PluginException:InvalidManifest', + array($this->getPluginID()))); + } + + // set manifest api version + if (isset($manifest_obj->attributes['xmlns'])) { + $namespace = $manifest_obj->attributes['xmlns']; + $version = str_replace($this->namespace_root, '', $namespace); + } else { + $version = 1.7; + } + + $this->apiVersion = $version; + + $parser_class_name = 'ElggPluginManifestParser' . str_replace('.', '', $this->apiVersion); + + // @todo currently the autoloader freaks out if a class doesn't exist. + try { + $class_exists = class_exists($parser_class_name); + } catch (Exception $e) { + $class_exists = false; + } + + if ($class_exists) { + $this->parser = new $parser_class_name($manifest_obj, $this); + } else { + throw new PluginException(elgg_echo('PluginException:NoAvailableParser', + array($this->apiVersion, $this->getPluginID()))); + } + + if (!$this->parser->parse()) { + throw new PluginException(elgg_echo('PluginException:ParserError', + array($this->apiVersion, $this->getPluginID()))); + } + + return true; + } + + /** + * Returns the API version in use. + * + * @return int + */ + public function getApiVersion() { + return $this->apiVersion; + } + + /** + * Returns the plugin ID. + * + * @return string + */ + public function getPluginID() { + if ($this->pluginID) { + return $this->pluginID; + } else { + return elgg_echo('unknown'); + } + } + + /** + * Returns the manifest array. + * + * Used for backward compatibility. Specific + * methods should be called instead. + * + * @return array + */ + public function getManifest() { + return $this->parser->getManifest(); + } + + /*************************************** + * Parsed and Normalized Manifest Data * + ***************************************/ + + /** + * Returns the plugin name + * + * @return string + */ + public function getName() { + $name = $this->parser->getAttribute('name'); + + if (!$name && $this->pluginID) { + $name = ucwords(str_replace('_', ' ', $this->pluginID)); + } + + return $name; + } + + + /** + * Return the description + * + * @return string + */ + public function getDescription() { + return $this->parser->getAttribute('description'); + } + + /** + * Return the short description + * + * @return string + */ + public function getBlurb() { + $blurb = $this->parser->getAttribute('blurb'); + + if (!$blurb) { + $blurb = elgg_get_excerpt($this->getDescription()); + } + + return $blurb; + } + + /** + * Returns the license + * + * @return string + */ + public function getLicense() { + // license vs licence. Use license. + $en_us = $this->parser->getAttribute('license'); + if ($en_us) { + return $en_us; + } else { + return $this->parser->getAttribute('licence'); + } + } + + /** + * Returns the repository url + * + * @return string + */ + public function getRepositoryURL() { + return $this->parser->getAttribute('repository'); + } + + /** + * Returns the bug tracker page + * + * @return string + */ + public function getBugTrackerURL() { + return $this->parser->getAttribute('bugtracker'); + } + + /** + * Returns the donations page + * + * @return string + */ + public function getDonationsPageURL() { + return $this->parser->getAttribute('donations'); + } + + /** + * Returns the version of the plugin. + * + * @return float + */ + public function getVersion() { + return $this->parser->getAttribute('version'); + } + + /** + * Returns the plugin author. + * + * @return string + */ + public function getAuthor() { + return $this->parser->getAttribute('author'); + } + + /** + * Return the copyright + * + * @return string + */ + public function getCopyright() { + return $this->parser->getAttribute('copyright'); + } + + /** + * Return the website + * + * @return string + */ + public function getWebsite() { + return $this->parser->getAttribute('website'); + } + + /** + * Return the categories listed for this plugin + * + * @return array + */ + public function getCategories() { + $bundled_plugins = array('blog', 'bookmarks', 'categories', + 'custom_index', 'dashboard', 'developers', 'diagnostics', + 'embed', 'externalpages', 'file', 'garbagecollector', + 'groups', 'htmlawed', 'invitefriends', 'likes', + 'logbrowser', 'logrotate', 'members', 'messageboard', + 'messages', 'notifications', 'oauth_api', 'pages', 'profile', + 'reportedcontent', 'search', 'tagcloud', 'thewire', 'tinymce', + 'twitter', 'twitter_api', 'uservalidationbyemail', 'zaudio', + ); + + $cats = $this->parser->getAttribute('category'); + + if (!$cats) { + $cats = array(); + } + + if (in_array('bundled', $cats) && !in_array($this->getPluginID(), $bundled_plugins)) { + unset($cats[array_search('bundled', $cats)]); + } + + return $cats; + } + + /** + * Return the screenshots listed. + * + * @return array + */ + public function getScreenshots() { + $ss = $this->parser->getAttribute('screenshot'); + + if (!$ss) { + $ss = array(); + } + + $normalized = array(); + foreach ($ss as $s) { + $normalized[] = $this->buildStruct($this->screenshotStruct, $s); + } + + return $normalized; + } + + /** + * Return the list of provides by this plugin. + * + * @return array + */ + public function getProvides() { + // normalize for 1.7 + if ($this->getApiVersion() < 1.8) { + $provides = array(); + } else { + $provides = $this->parser->getAttribute('provides'); + } + + if (!$provides) { + $provides = array(); + } + + // always provide ourself if we can + if ($this->pluginID) { + $provides[] = array( + 'type' => 'plugin', + 'name' => $this->getPluginID(), + 'version' => $this->getVersion() + ); + } + + $normalized = array(); + foreach ($provides as $provide) { + $normalized[] = $this->buildStruct($this->depsProvidesStruct, $provide); + } + + return $normalized; + } + + /** + * Returns the dependencies listed. + * + * @return array + */ + public function getRequires() { + // rewrite the 1.7 style elgg_version as a real requires. + if ($this->apiVersion < 1.8) { + $elgg_version = $this->parser->getAttribute('elgg_version'); + if ($elgg_version) { + $reqs = array( + array( + 'type' => 'elgg_version', + 'version' => $elgg_version, + 'comparison' => 'ge' + ) + ); + } else { + $reqs = array(); + } + } else { + $reqs = $this->parser->getAttribute('requires'); + } + + if (!$reqs) { + $reqs = array(); + } + + $normalized = array(); + foreach ($reqs as $req) { + $normalized[] = $this->normalizeDep($req); + } + + return $normalized; + } + + /** + * Returns the suggests elements. + * + * @return array + */ + public function getSuggests() { + $suggests = $this->parser->getAttribute('suggests'); + + if (!$suggests) { + $suggests = array(); + } + + $normalized = array(); + foreach ($suggests as $suggest) { + $normalized[] = $this->normalizeDep($suggest); + } + + return $normalized; + } + + /** + * Normalizes a dependency array using the defined structs. + * Can be used with either requires or suggests. + * + * @param array $dep A dependency array. + * @return array The normalized deps array. + */ + private function normalizeDep($dep) { + switch ($dep['type']) { + case 'elgg_version': + case 'elgg_release': + $struct = $this->depsStructElgg; + break; + + case 'plugin': + $struct = $this->depsStructPlugin; + break; + + case 'priority': + $struct = $this->depsStructPriority; + break; + + case 'php_extension': + $struct = $this->depsStructPhpExtension; + break; + + case 'php_ini': + $struct = $this->depsStructPhpIni; + + // also normalize boolean values + if (isset($dep['value'])) { + switch (strtolower($dep['value'])) { + case 'yes': + case 'true': + case 'on': + case 1: + $dep['value'] = 1; + break; + + case 'no': + case 'false': + case 'off': + case 0: + case '': + $dep['value'] = 0; + break; + } + } + break; + default: + // unrecognized so we just return the raw dependency + return $dep; + } + + $normalized_dep = $this->buildStruct($struct, $dep); + + // normalize comparison operators + if (isset($normalized_dep['comparison'])) { + switch ($normalized_dep['comparison']) { + case '<': + $normalized_dep['comparison'] = 'lt'; + break; + + case '<=': + $normalized_dep['comparison'] = 'le'; + break; + + case '>': + $normalized_dep['comparison'] = 'gt'; + break; + + case '>=': + $normalized_dep['comparison'] = 'ge'; + break; + + case '==': + case 'eq': + $normalized_dep['comparison'] = '='; + break; + + case '<>': + case 'ne': + $normalized_dep['comparison'] = '!='; + break; + } + } + + return $normalized_dep; + } + + /** + * Returns the conflicts listed + * + * @return array + */ + public function getConflicts() { + // normalize for 1.7 + if ($this->getApiVersion() < 1.8) { + $conflicts = array(); + } else { + $conflicts = $this->parser->getAttribute('conflicts'); + } + + if (!$conflicts) { + $conflicts = array(); + } + + $normalized = array(); + + foreach ($conflicts as $conflict) { + $normalized[] = $this->buildStruct($this->depsConflictsStruct, $conflict); + } + + return $normalized; + } + + /** + * Should this plugin be activated when Elgg is installed + * + * @return bool + */ + public function getActivateOnInstall() { + $activate = $this->parser->getAttribute('activate_on_install'); + switch (strtolower($activate)) { + case 'yes': + case 'true': + case 'on': + case 1: + return true; + + case 'no': + case 'false': + case 'off': + case 0: + case '': + return false; + } + } + + /** + * Normalizes an array into the structure specified + * + * @param array $struct The struct to normalize $element to. + * @param array $array The array + * + * @return array + */ + protected function buildStruct(array $struct, array $array) { + $return = array(); + + foreach ($struct as $index => $default) { + $return[$index] = elgg_extract($index, $array, $default); + } + + return $return; + } + + /** + * Returns a category's friendly name. This can be localized by + * defining the string 'admin:plugins:category:<category>'. If no + * localization is found, returns the category with _ and - converted to ' ' + * and then ucwords()'d. + * + * @param str $category The category as defined in the manifest. + * @return str A human-readable category + */ + static public function getFriendlyCategory($category) { + $cat_raw_string = "admin:plugins:category:$category"; + $cat_display_string = elgg_echo($cat_raw_string); + if ($cat_display_string == $cat_raw_string) { + $category = str_replace(array('-', '_'), ' ', $category); + $cat_display_string = ucwords($category); + } + return $cat_display_string; + } +} diff --git a/engine/classes/ElggPluginManifestParser.php b/engine/classes/ElggPluginManifestParser.php new file mode 100644 index 000000000..af152b561 --- /dev/null +++ b/engine/classes/ElggPluginManifestParser.php @@ -0,0 +1,102 @@ +<?php +/** + * Parent class for manifest parsers. + * + * Converts manifest.xml files or strings to an array. + * + * This should be extended by a class that does the actual work + * to convert based on the manifest.xml version. + * + * This class only parses XML to an XmlEntity object and + * an array. The array should be used primarily to extract + * information since it is quicker to parse once and store + * values from the XmlElement object than to parse the object + * each time. + * + * The array should be an exact representation of the manifest.xml + * file or string. Any normalization needs to be done in the + * calling class / function. + * + * @package Elgg.Core + * @subpackage Plugins + * @since 1.8 + */ +abstract class ElggPluginManifestParser { + /** + * The XmlElement object + * + * @var XmlElement + */ + protected $manifestObject; + + /** + * The manifest array + * + * @var array + */ + protected $manifest; + + /** + * All valid manifest attributes with default values. + * + * @var array + */ + protected $validAttributes; + + /** + * The object we're doing parsing for. + * + * @var object + */ + protected $caller; + + /** + * Loads the manifest XML to be parsed. + * + * @param ElggXmlElement $xml The Manifest XML object to be parsed + * @param object $caller The object calling this parser. + */ + public function __construct(ElggXMLElement $xml, $caller) { + $this->manifestObject = $xml; + $this->caller = $caller; + } + + /** + * Returns the manifest XML object + * + * @return XmlElement + */ + public function getManifestObject() { + return $this->manifestObject; + } + + /** + * Return the parsed manifest array + * + * @return array + */ + public function getManifest() { + return $this->manifest; + } + + /** + * Return an attribute in the manifest. + * + * @param string $name Attribute name + * @return mixed + */ + public function getAttribute($name) { + if (in_array($name, $this->validAttributes) && isset($this->manifest[$name])) { + return $this->manifest[$name]; + } + + return false; + } + + /** + * Parse the XML object into an array + * + * @return bool + */ + abstract public function parse(); +} diff --git a/engine/classes/ElggPluginManifestParser17.php b/engine/classes/ElggPluginManifestParser17.php new file mode 100644 index 000000000..5658ee804 --- /dev/null +++ b/engine/classes/ElggPluginManifestParser17.php @@ -0,0 +1,82 @@ +<?php +/** + * Plugin manifest.xml parser for Elgg 1.7 and lower. + * + * @package Elgg.Core + * @subpackage Plugins + * @since 1.8 + */ +class ElggPluginManifestParser17 extends ElggPluginManifestParser { + /** + * The valid top level attributes and defaults for a 1.7 manifest + */ + protected $validAttributes = array( + 'author', 'version', 'description', 'website', + 'copyright', 'license', 'licence', 'elgg_version', + + // were never really used and not enforced in code. + 'requires', 'recommends', 'conflicts', + + // not a 1.7 field, but we need it + 'name', + ); + + /** + * Parse a manifest object from 1.7 or earlier. + * + * @return void + */ + public function parse() { + if (!isset($this->manifestObject->children)) { + return false; + } + + $elements = array(); + + foreach ($this->manifestObject->children as $element) { + $key = $element->attributes['key']; + $value = $element->attributes['value']; + + // create arrays if multiple fields are set + if (array_key_exists($key, $elements)) { + if (!is_array($elements[$key])) { + $orig = $elements[$key]; + $elements[$key] = array($orig); + } + + $elements[$key][] = $value; + } else { + $elements[$key] = $value; + } + } + + if ($elements && !array_key_exists('name', $elements)) { + $elements['name'] = $this->caller->getName(); + } + + $this->manifest = $elements; + + if (!$this->manifest) { + return false; + } + + return true; + } + + /** + * Return an attribute in the manifest. + * + * Overrides ElggPluginManifestParser::getAttribute() because before 1.8 + * there were no rules...weeeeeeeee! + * + * @param string $name Attribute name + * @return mixed + */ + public function getAttribute($name) { + if (isset($this->manifest[$name])) { + return $this->manifest[$name]; + } + + return false; + } +} diff --git a/engine/classes/ElggPluginManifestParser18.php b/engine/classes/ElggPluginManifestParser18.php new file mode 100644 index 000000000..3b753f17b --- /dev/null +++ b/engine/classes/ElggPluginManifestParser18.php @@ -0,0 +1,97 @@ +<?php +/** + * Plugin manifest.xml parser for Elgg 1.8 and above. + * + * @package Elgg.Core + * @subpackage Plugins + * @since 1.8 + */ +class ElggPluginManifestParser18 extends ElggPluginManifestParser { + /** + * The valid top level attributes and defaults for a 1.8 manifest array. + * + * @var array + */ + protected $validAttributes = array( + 'name', 'author', 'version', 'blurb', 'description','website', + 'repository', 'bugtracker', 'donations', 'copyright', 'license', + 'requires', 'suggests', 'conflicts', 'provides', + 'screenshot', 'category', 'activate_on_install' + ); + + /** + * Required attributes for a valid 1.8 manifest + * + * @var array + */ + protected $requiredAttributes = array( + 'name', 'author', 'version', 'description', 'requires' + ); + + /** + * Parse a manifest object from 1.8 and later + * + * @return void + */ + public function parse() { + $parsed = array(); + foreach ($this->manifestObject->children as $element) { + switch ($element->name) { + // single elements + case 'blurb': + case 'description': + case 'name': + case 'author': + case 'version': + case 'website': + case 'copyright': + case 'license': + case 'repository': + case 'bugtracker': + case 'donations': + case 'activate_on_install': + $parsed[$element->name] = $element->content; + break; + + // arrays + case 'category': + $parsed[$element->name][] = $element->content; + break; + + // 3d arrays + case 'screenshot': + case 'provides': + case 'conflicts': + case 'requires': + case 'suggests': + if (!isset($element->children)) { + return false; + } + + $info = array(); + foreach ($element->children as $child_element) { + $info[$child_element->name] = $child_element->content; + } + + $parsed[$element->name][] = $info; + break; + } + } + + // check we have all the required fields + foreach ($this->requiredAttributes as $attr) { + if (!array_key_exists($attr, $parsed)) { + throw new PluginException(elgg_echo('PluginException:ParserErrorMissingRequiredAttribute', + array($attr, $this->caller->getPluginID()))); + } + } + + $this->manifest = $parsed; + + if (!$this->manifest) { + return false; + } + + return true; + } +} diff --git a/engine/classes/ElggPluginPackage.php b/engine/classes/ElggPluginPackage.php new file mode 100644 index 000000000..37eb4bf4d --- /dev/null +++ b/engine/classes/ElggPluginPackage.php @@ -0,0 +1,640 @@ +<?php +/** + * Manages plugin packages under mod. + * + * @todo This should eventually be merged into ElggPlugin. + * Currently ElggPlugin objects are only used to get and save + * plugin settings and user settings, so not every plugin + * has an ElggPlugin object. It's not implemented in ElggPlugin + * right now because of conflicts with at least the constructor, + * enable(), disable(), and private settings. + * + * Around 1.9 or so we should each plugin over to using + * ElggPlugin and merge ElggPluginPackage and ElggPlugin. + * + * @package Elgg.Core + * @subpackage Plugins + * @since 1.8 + */ +class ElggPluginPackage { + + /** + * The required files in the package + * + * @var array + */ + private $requiredFiles = array( + 'start.php', 'manifest.xml' + ); + + /** + * The optional files that can be read and served through the markdown page handler + * @var array + */ + private $textFiles = array( + 'README.txt', 'CHANGES.txt', + 'INSTALL.txt', 'COPYRIGHT.txt', 'LICENSE.txt', + + 'README', 'README.md', 'README.markdown' + ); + + /** + * Valid types for provides. + * + * @var array + */ + private $providesSupportedTypes = array( + 'plugin', 'php_extension' + ); + + /** + * The type of requires/conflicts supported + * + * @var array + */ + private $depsSupportedTypes = array( + 'elgg_version', 'elgg_release', 'php_extension', 'php_ini', 'plugin', 'priority', + ); + + /** + * An invalid plugin error. + */ + private $errorMsg = ''; + + /** + * Any dependencies messages + */ + private $depsMsgs = array(); + + /** + * The plugin's manifest object + * + * @var ElggPluginManifest + */ + protected $manifest; + + /** + * The plugin's full path + * + * @var string + */ + protected $path; + + /** + * Is the plugin valid? + * + * @var mixed Bool after validation check, null before. + */ + protected $valid = null; + + /** + * The plugin ID (dir name) + * + * @var string + */ + protected $id; + + /** + * Load a plugin package from mod/$id or by full path. + * + * @param string $plugin The ID (directory name) or full path of the plugin. + * @param bool $validate Automatically run isValid()? + * + * @throws PluginException + */ + public function __construct($plugin, $validate = true) { + $plugin_path = elgg_get_plugins_path(); + // @todo wanted to avoid another is_dir() call here. + // should do some profiling to see how much it affects + if (strpos($plugin, $plugin_path) === 0 || is_dir($plugin)) { + // this is a path + $path = sanitise_filepath($plugin); + + // the id is the last element of the array + $path_array = explode('/', trim($path, '/')); + $id = array_pop($path_array); + } else { + // this is a plugin id + // strict plugin names + if (preg_match('/[^a-z0-9\.\-_]/i', $plugin)) { + throw new PluginException(elgg_echo('PluginException:InvalidID', array($plugin))); + } + + $path = "{$plugin_path}$plugin/"; + $id = $plugin; + } + + if (!is_dir($path)) { + throw new PluginException(elgg_echo('PluginException:InvalidPath', array($path))); + } + + $this->path = $path; + $this->id = $id; + + if ($validate && !$this->isValid()) { + if ($this->errorMsg) { + throw new PluginException(elgg_echo('PluginException:InvalidPlugin:Details', + array($plugin, $this->errorMsg))); + } else { + throw new PluginException(elgg_echo('PluginException:InvalidPlugin', array($plugin))); + } + } + + return true; + } + + /******************************** + * Validation and sanity checks * + ********************************/ + + /** + * Checks if this is a valid Elgg plugin. + * + * Checks for requires files as defined at the start of this + * class. Will check require manifest fields via ElggPluginManifest + * for Elgg 1.8 plugins. + * + * @note This doesn't check dependencies or conflicts. + * Use {@link ElggPluginPackage::canActivate()} or + * {@link ElggPluginPackage::checkDependencies()} for that. + * + * @return bool + */ + public function isValid() { + if (isset($this->valid)) { + return $this->valid; + } + + // check required files. + $have_req_files = true; + foreach ($this->requiredFiles as $file) { + if (!is_readable($this->path . $file)) { + $have_req_files = false; + $this->errorMsg = + elgg_echo('ElggPluginPackage:InvalidPlugin:MissingFile', array($file)); + break; + } + } + + // check required files + if (!$have_req_files) { + return $this->valid = false; + } + + // check for valid manifest. + if (!$this->loadManifest()) { + return $this->valid = false; + } + + // can't require or conflict with yourself or something you provide. + // make sure provides are all valid. + if (!$this->isSaneDeps()) { + return $this->valid = false; + } + + return $this->valid = true; + } + + /** + * Check the plugin doesn't require or conflict with itself + * or something provides. Also check that it only list + * valid provides. Deps are checked in checkDependencies() + * + * @note Plugins always provide themselves. + * + * @todo Don't let them require and conflict the same thing + * + * @return bool + */ + private function isSaneDeps() { + // protection against plugins with no manifest file + if (!$this->getManifest()) { + return false; + } + + // Note: $conflicts and $requires are not unused. They're called dynamically + $conflicts = $this->getManifest()->getConflicts(); + $requires = $this->getManifest()->getRequires(); + $provides = $this->getManifest()->getProvides(); + + foreach ($provides as $provide) { + // only valid provide types + if (!in_array($provide['type'], $this->providesSupportedTypes)) { + $this->errorMsg = + elgg_echo('ElggPluginPackage:InvalidPlugin:InvalidProvides', array($provide['type'])); + return false; + } + + // doesn't conflict or require any of its provides + $name = $provide['name']; + foreach (array('conflicts', 'requires') as $dep_type) { + foreach (${$dep_type} as $dep) { + if (!in_array($dep['type'], $this->depsSupportedTypes)) { + $this->errorMsg = + elgg_echo('ElggPluginPackage:InvalidPlugin:InvalidDependency', array($dep['type'])); + return false; + } + + // make sure nothing is providing something it conflicts or requires. + if (isset($dep['name']) && $dep['name'] == $name) { + $version_compare = version_compare($provide['version'], $dep['version'], $dep['comparison']); + + if ($version_compare) { + $this->errorMsg = + elgg_echo('ElggPluginPackage:InvalidPlugin:CircularDep', + array($dep['type'], $dep['name'], $this->id)); + + return false; + } + } + } + } + } + + return true; + } + + + /************ + * Manifest * + ************/ + + /** + * Returns a parsed manifest file. + * + * @return ElggPluginManifest + */ + public function getManifest() { + if (!$this->manifest) { + if (!$this->loadManifest()) { + return false; + } + } + + return $this->manifest; + } + + /** + * Loads the manifest into this->manifest as an + * ElggPluginManifest object. + * + * @return bool + */ + private function loadManifest() { + $file = $this->path . 'manifest.xml'; + + try { + $this->manifest = new ElggPluginManifest($file, $this->id); + } catch (Exception $e) { + $this->errorMsg = $e->getMessage(); + return false; + } + + if ($this->manifest instanceof ElggPluginManifest) { + return true; + } + + $this->errorMsg = elgg_echo('unknown_error'); + return false; + } + + /**************** + * Readme Files * + ***************/ + + /** + * Returns an array of present and readable text files + * + * @return array + */ + public function getTextFilenames() { + return $this->textFiles; + } + + /*********************** + * Dependencies system * + ***********************/ + + /** + * Returns if the Elgg system meets the plugin's dependency + * requirements. This includes both requires and conflicts. + * + * Full reports can be requested. The results are returned + * as an array of arrays in the form array( + * 'type' => requires|conflicts, + * 'dep' => array( dependency array ), + * 'status' => bool if depedency is met, + * 'comment' => optional comment to display to the user. + * ) + * + * @param bool $full_report Return a full report. + * @return bool|array + */ + public function checkDependencies($full_report = false) { + // Note: $conflicts and $requires are not unused. They're called dynamically + $requires = $this->getManifest()->getRequires(); + $conflicts = $this->getManifest()->getConflicts(); + + $enabled_plugins = elgg_get_plugins('active'); + $this_id = $this->getID(); + $report = array(); + + // first, check if any active plugin conflicts with us. + foreach ($enabled_plugins as $plugin) { + $temp_conflicts = array(); + $temp_manifest = $plugin->getManifest(); + if ($temp_manifest instanceof ElggPluginManifest) { + $temp_conflicts = $plugin->getManifest()->getConflicts(); + } + foreach ($temp_conflicts as $conflict) { + if ($conflict['type'] == 'plugin' && $conflict['name'] == $this_id) { + $result = $this->checkDepPlugin($conflict, $enabled_plugins, false); + + // rewrite the conflict to show the originating plugin + $conflict['name'] = $plugin->getManifest()->getName(); + + if (!$full_report && !$result['status']) { + $this->errorMsg = "Conflicts with plugin \"{$plugin->getManifest()->getName()}\"."; + return $result['status']; + } else { + $report[] = array( + 'type' => 'conflicted', + 'dep' => $conflict, + 'status' => $result['status'], + 'value' => $this->getManifest()->getVersion() + ); + } + } + } + } + + $check_types = array('requires', 'conflicts'); + + if ($full_report) { + // Note: $suggests is not unused. It's called dynamically + $suggests = $this->getManifest()->getSuggests(); + $check_types[] = 'suggests'; + } + + foreach ($check_types as $dep_type) { + $inverse = ($dep_type == 'conflicts') ? true : false; + + foreach (${$dep_type} as $dep) { + switch ($dep['type']) { + case 'elgg_version': + $result = $this->checkDepElgg($dep, get_version(), $inverse); + break; + + case 'elgg_release': + $result = $this->checkDepElgg($dep, get_version(true), $inverse); + break; + + case 'plugin': + $result = $this->checkDepPlugin($dep, $enabled_plugins, $inverse); + break; + + case 'priority': + $result = $this->checkDepPriority($dep, $enabled_plugins, $inverse); + break; + + case 'php_extension': + $result = $this->checkDepPhpExtension($dep, $inverse); + break; + + case 'php_ini': + $result = $this->checkDepPhpIni($dep, $inverse); + break; + } + + // unless we're doing a full report, break as soon as we fail. + if (!$full_report && !$result['status']) { + $this->errorMsg = "Missing dependencies."; + return $result['status']; + } else { + // build report element and comment + $report[] = array( + 'type' => $dep_type, + 'dep' => $dep, + 'status' => $result['status'], + 'value' => $result['value'] + ); + } + } + } + + if ($full_report) { + // add provides to full report + $provides = $this->getManifest()->getProvides(); + + foreach ($provides as $provide) { + $report[] = array( + 'type' => 'provides', + 'dep' => $provide, + 'status' => true, + 'value' => '' + ); + } + + return $report; + } + + return true; + } + + /** + * Checks if $plugins meets the requirement by $dep. + * + * @param array $dep An Elgg manifest.xml deps array + * @param array $plugins A list of plugins as returned by elgg_get_plugins(); + * @param bool $inverse Inverse the results to use as a conflicts. + * @return bool + */ + private function checkDepPlugin(array $dep, array $plugins, $inverse = false) { + $r = elgg_check_plugins_provides('plugin', $dep['name'], $dep['version'], $dep['comparison']); + + if ($inverse) { + $r['status'] = !$r['status']; + } + + return $r; + } + + /** + * Checks if $plugins meets the requirement by $dep. + * + * @param array $dep An Elgg manifest.xml deps array + * @param array $plugins A list of plugins as returned by elgg_get_plugins(); + * @param bool $inverse Inverse the results to use as a conflicts. + * @return bool + */ + private function checkDepPriority(array $dep, array $plugins, $inverse = false) { + // grab the ElggPlugin using this package. + $plugin_package = elgg_get_plugin_from_id($this->getID()); + $plugin_priority = $plugin_package->getPriority(); + $test_plugin = elgg_get_plugin_from_id($dep['plugin']); + + // If this isn't a plugin or the plugin isn't installed or active + // priority doesn't matter. Use requires to check if a plugin is active. + if (!$plugin_package || !$test_plugin || !$test_plugin->isActive()) { + return array( + 'status' => true, + 'value' => 'uninstalled' + ); + } + + $test_plugin_priority = $test_plugin->getPriority(); + + switch ($dep['priority']) { + case 'before': + $status = $plugin_priority < $test_plugin_priority; + break; + + case 'after': + $status = $plugin_priority > $test_plugin_priority; + break; + + default; + $status = false; + } + + // get the current value + if ($plugin_priority < $test_plugin_priority) { + $value = 'before'; + } else { + $value = 'after'; + } + + if ($inverse) { + $status = !$status; + } + + return array( + 'status' => $status, + 'value' => $value + ); + } + + /** + * Checks if $elgg_version meets the requirement by $dep. + * + * @param array $dep An Elgg manifest.xml deps array + * @param array $elgg_version An Elgg version (either YYYYMMDDXX or X.Y.Z) + * @param bool $inverse Inverse the result to use as a conflicts. + * @return bool + */ + private function checkDepElgg(array $dep, $elgg_version, $inverse = false) { + $status = version_compare($elgg_version, $dep['version'], $dep['comparison']); + + if ($inverse) { + $status = !$status; + } + + return array( + 'status' => $status, + 'value' => $elgg_version + ); + } + + /** + * Checks if the PHP extension in $dep is loaded. + * + * @todo Can this be merged with the plugin checker? + * + * @param array $dep An Elgg manifest.xml deps array + * @param bool $inverse Inverse the result to use as a conflicts. + * @return array An array in the form array( + * 'status' => bool + * 'value' => string The version provided + * ) + */ + private function checkDepPhpExtension(array $dep, $inverse = false) { + $name = $dep['name']; + $version = $dep['version']; + $comparison = $dep['comparison']; + + // not enabled. + $status = extension_loaded($name); + + // enabled. check version. + $ext_version = phpversion($name); + + if ($status) { + // some extensions (like gd) don't provide versions. neat. + // don't check version info and return a lie. + if ($ext_version && $version) { + $status = version_compare($ext_version, $version, $comparison); + } + + if (!$ext_version) { + $ext_version = '???'; + } + } + + // some php extensions can be emulated, so check provides. + if ($status == false) { + $provides = elgg_check_plugins_provides('php_extension', $name, $version, $comparison); + $status = $provides['status']; + $ext_version = $provides['value']; + } + + if ($inverse) { + $status = !$status; + } + + return array( + 'status' => $status, + 'value' => $ext_version + ); + } + + /** + * Check if the PHP ini setting satisfies $dep. + * + * @param array $dep An Elgg manifest.xml deps array + * @param bool $inverse Inverse the result to use as a conflicts. + * @return bool + */ + private function checkDepPhpIni($dep, $inverse = false) { + $name = $dep['name']; + $value = $dep['value']; + $comparison = $dep['comparison']; + + // ini_get() normalizes truthy values to 1 but falsey values to 0 or ''. + // version_compare() considers '' < 0, so normalize '' to 0. + // ElggPluginManifest normalizes all bool values and '' to 1 or 0. + $setting = ini_get($name); + + if ($setting === '') { + $setting = 0; + } + + $status = version_compare($setting, $value, $comparison); + + if ($inverse) { + $status = !$status; + } + + return array( + 'status' => $status, + 'value' => $setting + ); + } + + /** + * Returns the Plugin ID + * + * @return string + */ + public function getID() { + return $this->id; + } + + /** + * Returns the last error message. + * + * @return string + */ + public function getError() { + return $this->errorMsg; + } +} diff --git a/engine/classes/ElggPriorityList.php b/engine/classes/ElggPriorityList.php new file mode 100644 index 000000000..416df885c --- /dev/null +++ b/engine/classes/ElggPriorityList.php @@ -0,0 +1,366 @@ +<?php +/** + * Iterate over elements in a specific priority. + * + * $pl = new ElggPriorityList(); + * $pl->add('Element 0'); + * $pl->add('Element 10', 10); + * $pl->add('Element -10', -10); + * + * foreach ($pl as $priority => $element) { + * var_dump("$priority => $element"); + * } + * + * Yields: + * -10 => Element -10 + * 0 => Element 0 + * 10 => Element 10 + * + * Collisions on priority are handled by inserting the element at or as close to the + * requested priority as possible: + * + * $pl = new ElggPriorityList(); + * $pl->add('Element 5', 5); + * $pl->add('Colliding element 5', 5); + * $pl->add('Another colliding element 5', 5); + * + * foreach ($pl as $priority => $element) { + * var_dump("$priority => $element"); + * } + * + * Yields: + * 5 => 'Element 5', + * 6 => 'Colliding element 5', + * 7 => 'Another colliding element 5' + * + * You can do priority lookups by element: + * + * $pl = new ElggPriorityList(); + * $pl->add('Element 0'); + * $pl->add('Element -5', -5); + * $pl->add('Element 10', 10); + * $pl->add('Element -10', -10); + * + * $priority = $pl->getPriority('Element -5'); + * + * Or element lookups by priority. + * $element = $pl->getElement(-5); + * + * To remove elements, pass the element. + * $pl->remove('Element -10'); + * + * To check if an element exists: + * $pl->contains('Element -5'); + * + * To move an element: + * $pl->move('Element -5', -3); + * + * ElggPriorityList only tracks priority. No checking is done in ElggPriorityList for duplicates or + * updating. If you need to track this use objects and an external map: + * + * function elgg_register_something($id, $display_name, $location, $priority = 500) { + * // $id => $element. + * static $map = array(); + * static $list; + * + * if (!$list) { + * $list = new ElggPriorityList(); + * } + * + * // update if already registered. + * if (isset($map[$id])) { + * $element = $map[$id]; + * // move it first because we have to pass the original element. + * if (!$list->move($element, $priority)) { + * return false; + * } + * $element->display_name = $display_name; + * $element->location = $location; + * } else { + * $element = new stdClass(); + * $element->display_name = $display_name; + * $element->location = $location; + * if (!$list->add($element, $priority)) { + * return false; + * } + * $map[$id] = $element; + * } + * + * return true; + * } + * + * @package Elgg.Core + * @subpackage Helpers + */ +class ElggPriorityList + implements Iterator, Countable { + + /** + * The list of elements + * + * @var array + */ + private $elements = array(); + + /** + * Create a new priority list. + * + * @param array $elements An optional array of priorities => element + */ + public function __construct(array $elements = array()) { + if ($elements) { + foreach ($elements as $priority => $element) { + $this->add($element, $priority); + } + } + } + + /** + * Adds an element to the list. + * + * @warning This returns the priority at which the element was added, which can be 0. Use + * !== false to check for success. + * + * @param mixed $element The element to add to the list. + * @param mixed $priority Priority to add the element. In priority collisions, the original element + * maintains its priority and the new element is to the next available + * slot, taking into consideration all previously registered elements. + * Negative elements are accepted. + * @param bool $exact unused + * @return int The priority of the added element. + * @todo remove $exact or implement it. Note we use variable name strict below. + */ + public function add($element, $priority = null, $exact = false) { + if ($priority !== null && !is_numeric($priority)) { + return false; + } else { + $priority = $this->getNextPriority($priority); + } + + $this->elements[$priority] = $element; + $this->sorted = false; + return $priority; + } + + /** + * Removes an element from the list. + * + * @warning The element must have the same attributes / values. If using $strict, it must have + * the same types. array(10) will fail in strict against array('10') (str vs int). + * + * @param mixed $element The element to remove from the list + * @param bool $strict Whether to check the type of the element match + * @return bool + */ + public function remove($element, $strict = false) { + $index = array_search($element, $this->elements, $strict); + if ($index !== false) { + unset($this->elements[$index]); + return true; + } else { + return false; + } + } + + /** + * Move an existing element to a new priority. + * + * @param mixed $element The element to move + * @param int $new_priority The new priority for the element + * @param bool $strict Whether to check the type of the element match + * @return bool + */ + public function move($element, $new_priority, $strict = false) { + $new_priority = (int) $new_priority; + + $current_priority = $this->getPriority($element, $strict); + if ($current_priority === false) { + return false; + } + + if ($current_priority == $new_priority) { + return true; + } + + // move the actual element so strict operations still work + $element = $this->getElement($current_priority); + unset($this->elements[$current_priority]); + return $this->add($element, $new_priority); + } + + /** + * Returns the elements + * + * @return array + */ + public function getElements() { + $this->sortIfUnsorted(); + return $this->elements; + } + + /** + * Sort the elements optionally by a callback function. + * + * If no user function is provided the elements are sorted by priority registered. + * + * The callback function should accept the array of elements as the first + * argument and should return a sorted array. + * + * This function can be called multiple times. + * + * @param callback $callback The callback for sorting. Numeric sorting is the default. + * @return bool + */ + public function sort($callback = null) { + if (!$callback) { + ksort($this->elements, SORT_NUMERIC); + } else { + $sorted = call_user_func($callback, $this->elements); + + if (!$sorted) { + return false; + } + + $this->elements = $sorted; + } + + $this->sorted = true; + return true; + } + + /** + * Sort the elements if they haven't been sorted yet. + * + * @return bool + */ + private function sortIfUnsorted() { + if (!$this->sorted) { + return $this->sort(); + } + } + + /** + * Returns the next priority available. + * + * @param int $near Make the priority as close to $near as possible. + * @return int + */ + public function getNextPriority($near = 0) { + $near = (int) $near; + + while (array_key_exists($near, $this->elements)) { + $near++; + } + + return $near; + } + + /** + * Returns the priority of an element if it exists in the list. + * + * @warning This can return 0 if the element's priority is 0. + * + * @param mixed $element The element to check for. + * @param bool $strict Use strict checking? + * @return mixed False if the element doesn't exists, the priority if it does. + */ + public function getPriority($element, $strict = false) { + return array_search($element, $this->elements, $strict); + } + + /** + * Returns the element at $priority. + * + * @param int $priority The priority + * @return mixed The element or false on fail. + */ + public function getElement($priority) { + return (isset($this->elements[$priority])) ? $this->elements[$priority] : false; + } + + /** + * Returns if the list contains $element. + * + * @param mixed $element The element to check. + * @param bool $strict Use strict checking? + * @return bool + */ + public function contains($element, $strict = false) { + return $this->getPriority($element, $strict) !== false; + } + + + /********************** + * Interface methods * + **********************/ + + /** + * Iterator + */ + + /** + * PHP Iterator Interface + * + * @see Iterator::rewind() + * @return void + */ + public function rewind() { + $this->sortIfUnsorted(); + return reset($this->elements); + } + + /** + * PHP Iterator Interface + * + * @see Iterator::current() + * @return mixed + */ + public function current() { + $this->sortIfUnsorted(); + return current($this->elements); + } + + /** + * PHP Iterator Interface + * + * @see Iterator::key() + * @return int + */ + public function key() { + $this->sortIfUnsorted(); + return key($this->elements); + } + + /** + * PHP Iterator Interface + * + * @see Iterator::next() + * @return mixed + */ + public function next() { + $this->sortIfUnsorted(); + return next($this->elements); + } + + /** + * PHP Iterator Interface + * + * @see Iterator::valid() + * @return bool + */ + public function valid() { + $this->sortIfUnsorted(); + $key = key($this->elements); + return ($key !== NULL && $key !== FALSE); + } + + /** + * Countable interface + * + * @see Countable::count() + * @return int + */ + public function count() { + return count($this->elements); + } +}
\ No newline at end of file diff --git a/engine/classes/ElggRelationship.php b/engine/classes/ElggRelationship.php index 413527df3..d2e88882a 100644 --- a/engine/classes/ElggRelationship.php +++ b/engine/classes/ElggRelationship.php @@ -4,15 +4,21 @@ * * @package Elgg.Core * @subpackage Core + * + * @property int $id The unique identifier (read-only) + * @property int $guid_one The GUID of the subject of the relationship + * @property string $relationship The name of the relationship + * @property int $guid_two The GUID of the object of the relationship + * @property int $time_created A UNIX timestamp of when the relationship was created (read-only, set on first save) */ class ElggRelationship extends ElggData implements Importable { /** - * Construct a new site object, optionally from a given id value or row. + * Create a relationship object, optionally from a given id value or row. * - * @param mixed $id ElggRelationship id + * @param mixed $id ElggRelationship id, database row, or null for new relationship */ function __construct($id = null) { $this->initializeAttributes(); @@ -41,7 +47,7 @@ class ElggRelationship extends ElggData implements * @return mixed */ function get($name) { - if (isset($this->attributes[$name])) { + if (array_key_exists($name, $this->attributes)) { return $this->attributes[$name]; } @@ -65,6 +71,7 @@ class ElggRelationship extends ElggData implements * Save the relationship * * @return int the relationship id + * @throws IOException */ public function save() { if ($this->id > 0) { @@ -73,7 +80,7 @@ class ElggRelationship extends ElggData implements $this->id = add_entity_relationship($this->guid_one, $this->relationship, $this->guid_two); if (!$this->id) { - throw new IOException(sprintf(elgg_echo('IOException:UnableToSaveNew'), get_class())); + throw new IOException(elgg_echo('IOException:UnableToSaveNew', array(get_class()))); } return $this->id; @@ -136,14 +143,13 @@ class ElggRelationship extends ElggData implements /** * Import a relationship * - * @param array $data ODD data - * - * @return ElggRelationship - * - * @throws ImportException + * @param ODD $data ODD data + + * @return bool + * @throws ImportException|InvalidParameterException */ public function import(ODD $data) { - if (!($element instanceof ODDRelationship)) { + if (!($data instanceof ODDRelationship)) { throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnexpectedODDClass')); } @@ -168,12 +174,14 @@ class ElggRelationship extends ElggData implements // save $result = $this->save(); if (!$result) { - throw new ImportException(sprintf(elgg_echo('ImportException:ProblemSaving'), get_class())); + throw new ImportException(elgg_echo('ImportException:ProblemSaving', array(get_class()))); } - return $this; + return true; } } + + return false; } // SYSTEM LOG INTERFACE //////////////////////////////////////////////////////////// @@ -220,4 +228,4 @@ class ElggRelationship extends ElggData implements return $this->relationship; } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggRiverItem.php b/engine/classes/ElggRiverItem.php new file mode 100644 index 000000000..d3d09cd91 --- /dev/null +++ b/engine/classes/ElggRiverItem.php @@ -0,0 +1,115 @@ +<?php +/** + * River item class. + * + * @package Elgg.Core + * @subpackage Core + * + * @property int $id The unique identifier (read-only) + * @property int $subject_guid The GUID of the actor + * @property int $object_guid The GUID of the object + * @property int $annotation_id The ID of the annotation involved in the action + * @property string $type The type of one of the entities involved in the action + * @property string $subtype The subtype of one of the entities involved in the action + * @property string $action_type The name of the action + * @property string $view The view for displaying this river item + * @property int $access_id The visibility of the river item + * @property int $posted UNIX timestamp when the action occurred + */ +class ElggRiverItem { + public $id; + public $subject_guid; + public $object_guid; + public $annotation_id; + public $type; + public $subtype; + public $action_type; + public $access_id; + public $view; + public $posted; + + /** + * Construct a river item object given a database row. + * + * @param stdClass $object Object obtained from database + */ + function __construct($object) { + if (!($object instanceof stdClass)) { + // throw exception + } + + // the casting is to support typed serialization like json + $int_types = array('id', 'subject_guid', 'object_guid', 'annotation_id', 'access_id', 'posted'); + foreach ($object as $key => $value) { + if (in_array($key, $int_types)) { + $this->$key = (int)$value; + } else { + $this->$key = $value; + } + } + } + + /** + * Get the subject of this river item + * + * @return ElggEntity + */ + public function getSubjectEntity() { + return get_entity($this->subject_guid); + } + + /** + * Get the object of this river item + * + * @return ElggEntity + */ + public function getObjectEntity() { + return get_entity($this->object_guid); + } + + /** + * Get the Annotation for this river item + * + * @return ElggAnnotation + */ + public function getAnnotation() { + return elgg_get_annotation_from_id($this->annotation_id); + } + + /** + * Get the view used to display this river item + * + * @return string + */ + public function getView() { + return $this->view; + } + + /** + * Get the time this activity was posted + * + * @return int + */ + public function getPostedTime() { + return (int)$this->posted; + } + + /** + * Get the type of the object + * + * @return string 'river' + */ + public function getType() { + return 'river'; + } + + /** + * Get the subtype of the object + * + * @return string 'item' + */ + public function getSubtype() { + return 'item'; + } + +} diff --git a/engine/classes/ElggSession.php b/engine/classes/ElggSession.php index 1d59aaacc..9750f063e 100644 --- a/engine/classes/ElggSession.php +++ b/engine/classes/ElggSession.php @@ -54,7 +54,7 @@ class ElggSession implements ArrayAccess { * * @param mixed $key Name * - * @return void + * @return mixed */ function offsetGet($key) { if (!ElggSession::$__localcache) { @@ -70,7 +70,7 @@ class ElggSession implements ArrayAccess { } $value = NULL; - $value = trigger_plugin_hook('session:get', $key, NULL, $value); + $value = elgg_trigger_plugin_hook('session:get', $key, NULL, $value); ElggSession::$__localcache[$key] = $value; @@ -98,7 +98,7 @@ class ElggSession implements ArrayAccess { * * @param int $offset Offset * - * @return int + * @return bool */ function offsetExists($offset) { if (isset(ElggSession::$__localcache[$offset])) { @@ -112,6 +112,8 @@ class ElggSession implements ArrayAccess { if ($this->offsetGet($offset)) { return true; } + + return false; } @@ -132,10 +134,10 @@ class ElggSession implements ArrayAccess { * @param string $key Name * @param mixed $value Value * - * @return mixed + * @return void */ function set($key, $value) { - return $this->offsetSet($key, $value); + $this->offsetSet($key, $value); } /** @@ -143,9 +145,9 @@ class ElggSession implements ArrayAccess { * * @param string $key Name * - * @return bool + * @return void */ function del($key) { - return $this->offsetUnset($key); + $this->offsetUnset($key); } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggSharedMemoryCache.php b/engine/classes/ElggSharedMemoryCache.php index 6d6a3d625..f5f11d2c7 100644 --- a/engine/classes/ElggSharedMemoryCache.php +++ b/engine/classes/ElggSharedMemoryCache.php @@ -37,4 +37,4 @@ abstract class ElggSharedMemoryCache extends ElggCache { public function getNamespace() { return $this->namespace; } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggSite.php b/engine/classes/ElggSite.php index febd70be6..dd996fe98 100644 --- a/engine/classes/ElggSite.php +++ b/engine/classes/ElggSite.php @@ -21,23 +21,12 @@ * @package Elgg.Core * @subpackage DataMode.Site * @link http://docs.elgg.org/DataModel/Sites + * + * @property string $name The name or title of the website + * @property string $description A motto, mission statement, or description of the website + * @property string $url The root web address for the site, including trailing slash */ class ElggSite extends ElggEntity { - /** - * Initialise the attributes array. - * This is vital to distinguish between metadata and base parameters. - * - * Place your base parameters here. - * - * @deprecated 1.8 Use ElggSite::initializeAttributes() - * - * @return void - */ - protected function initialise_attributes() { - elgg_deprecated_notice('ElggSite::initialise_attributes() is deprecated by ::initializeAttributes()', 1.8); - - return $this->initializeAttributes(); - } /** * Initialise the attributes array. @@ -51,9 +40,9 @@ class ElggSite extends ElggEntity { parent::initializeAttributes(); $this->attributes['type'] = "site"; - $this->attributes['name'] = ""; - $this->attributes['description'] = ""; - $this->attributes['url'] = ""; + $this->attributes['name'] = NULL; + $this->attributes['description'] = NULL; + $this->attributes['url'] = NULL; $this->attributes['tables_split'] = 2; } @@ -68,8 +57,8 @@ class ElggSite extends ElggEntity { * - A URL as stored in ElggSite->url * - A DB result object with a guid property * - * @param mixed $guid If an int, load that GUID. If a db row then will attempt - * to load the rest of the data. + * @param mixed $guid If an int, load that GUID. If a db row then will + * load the rest of the data. * * @throws IOException If passed an incorrect guid * @throws InvalidParameterException If passed an Elgg* Entity that isn't an ElggSite @@ -77,38 +66,37 @@ class ElggSite extends ElggEntity { function __construct($guid = null) { $this->initializeAttributes(); + // compatibility for 1.7 api. + $this->initialise_attributes(false); + if (!empty($guid)) { - // Is $guid is a DB row - either a entity row, or a site table row. + // Is $guid is a DB entity table row if ($guid instanceof stdClass) { // Load the rest - if (!$this->load($guid->guid)) { - $msg = sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid); + if (!$this->load($guid)) { + $msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid)); throw new IOException($msg); } - - // Is $guid is an ElggSite? Use a copy constructor } else if ($guid instanceof ElggSite) { + // $guid is an ElggSite so this is a copy constructor elgg_deprecated_notice('This type of usage of the ElggSite constructor was deprecated. Please use the clone method.', 1.7); foreach ($guid->attributes as $key => $value) { $this->attributes[$key] = $value; } - - // Is this is an ElggEntity but not an ElggSite = ERROR! } else if ($guid instanceof ElggEntity) { + // @todo remove and just use else clause throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggSite')); - - // See if this is a URL } else if (strpos($guid, "http") !== false) { + // url so retrieve by url $guid = get_site_by_url($guid); foreach ($guid->attributes as $key => $value) { $this->attributes[$key] = $value; } - - // We assume if we have got this far, $guid is an int } else if (is_numeric($guid)) { + // $guid is a GUID so load if (!$this->load($guid)) { - throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid)); + throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid))); } } else { throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); @@ -119,35 +107,24 @@ class ElggSite extends ElggEntity { /** * Loads the full ElggSite when given a guid. * - * @param int $guid Guid of ElggSite entity + * @param mixed $guid GUID of ElggSite entity or database row object * * @return bool * @throws InvalidClassException */ protected function load($guid) { - // Test to see if we have the generic stuff - if (!parent::load($guid)) { - return false; - } + $attr_loader = new ElggAttributeLoader(get_class(), 'site', $this->attributes); + $attr_loader->requires_access_control = !($this instanceof ElggPlugin); + $attr_loader->secondary_loader = 'get_site_entity_as_row'; - // Check the type - if ($this->attributes['type'] != 'site') { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class()); - throw new InvalidClassException($msg); - } - - // Load missing data - $row = get_site_entity_as_row($guid); - if (($row) && (!$this->isFullyLoaded())) { - // If $row isn't a cached copy then increment the counter - $this->attributes['tables_loaded'] ++; + $attrs = $attr_loader->getRequiredAttributes($guid); + if (!$attrs) { + return false; } - // Now put these into the attributes array as core values - $objarray = (array) $row; - foreach ($objarray as $key => $value) { - $this->attributes[$key] = $value; - } + $this->attributes = $attrs; + $this->attributes['tables_loaded'] = 2; + _elgg_cache_entity($this); return true; } @@ -160,11 +137,20 @@ class ElggSite extends ElggEntity { * @return bool */ public function save() { + global $CONFIG; + // Save generic stuff if (!parent::save()) { return false; } + // make sure the site guid is set (if not, set to self) + if (!$this->get('site_guid')) { + $guid = $this->get('guid'); + update_data("UPDATE {$CONFIG->dbprefix}entities SET site_guid=$guid + WHERE guid=$guid"); + } + // Now save specific stuff return create_site_entity($this->get('guid'), $this->get('name'), $this->get('description'), $this->get('url')); @@ -192,31 +178,79 @@ class ElggSite extends ElggEntity { * * @note You cannot disable the current site. * - * @param string $reason Optional reason for disabling + * @param string $reason Optional reason for disabling + * @param bool $recursive Recursively disable all contained entities? * * @return bool * @throws SecurityException */ - public function disable($reason = "") { + public function disable($reason = "", $recursive = true) { global $CONFIG; if ($CONFIG->site->getGUID() == $this->guid) { throw new SecurityException('SecurityException:deletedisablecurrentsite'); } - return parent::disable($reason); + return parent::disable($reason, $recursive); } /** - * Returns an array of ElggUser entities who are members of the site. + * Gets an array of ElggUser entities who are members of the site. + * + * @param array $options An associative array for key => value parameters + * accepted by elgg_get_entities(). Common parameters + * include 'limit', and 'offset'. + * Note: this was $limit before version 1.8 + * @param int $offset Offset @deprecated parameter * - * @param int $limit Limit - * @param int $offset Offset + * @todo remove $offset in 2.0 * * @return array of ElggUsers */ - public function getMembers($limit = 10, $offset = 0) { - get_site_members($this->getGUID(), $limit, $offset); + public function getMembers($options = array(), $offset = 0) { + if (!is_array($options)) { + elgg_deprecated_notice("ElggSite::getMembers uses different arguments!", 1.8); + $options = array( + 'limit' => $options, + 'offset' => $offset, + ); + } + + $defaults = array( + 'site_guids' => ELGG_ENTITIES_ANY_VALUE, + 'relationship' => 'member_of_site', + 'relationship_guid' => $this->getGUID(), + 'inverse_relationship' => TRUE, + 'type' => 'user', + ); + + $options = array_merge($defaults, $options); + + return elgg_get_entities_from_relationship($options); + } + + /** + * List the members of this site + * + * @param array $options An associative array for key => value parameters + * accepted by elgg_list_entities(). Common parameters + * include 'full_view', 'limit', and 'offset'. + * + * @return string + * @since 1.8.0 + */ + public function listMembers($options = array()) { + $defaults = array( + 'site_guids' => ELGG_ENTITIES_ANY_VALUE, + 'relationship' => 'member_of_site', + 'relationship_guid' => $this->getGUID(), + 'inverse_relationship' => TRUE, + 'type' => 'user', + ); + + $options = array_merge($defaults, $options); + + return elgg_list_entities_from_relationship($options); } /** @@ -244,6 +278,9 @@ class ElggSite extends ElggEntity { /** * Returns an array of ElggObject entities that belong to the site. * + * @warning This only returns objects that have been explicitly added to the + * site through addObject() + * * @param string $subtype Entity subtype * @param int $limit Limit * @param int $offset Offset @@ -251,7 +288,7 @@ class ElggSite extends ElggEntity { * @return array */ public function getObjects($subtype = "", $limit = 10, $offset = 0) { - get_site_objects($this->getGUID(), $subtype, $limit, $offset); + return get_site_objects($this->getGUID(), $subtype, $limit, $offset); } /** @@ -284,9 +321,10 @@ class ElggSite extends ElggEntity { * @param int $offset Offset * * @return unknown - * @todo Unimplemented + * @deprecated 1.8 Was never implemented */ public function getCollections($subtype = "", $limit = 10, $offset = 0) { + elgg_deprecated_notice("ElggSite::getCollections() is deprecated", 1.8); get_site_collections($this->getGUID(), $subtype, $limit, $offset); } @@ -315,17 +353,36 @@ class ElggSite extends ElggEntity { * @link http://docs.elgg.org/Tutorials/WalledGarden * * @return void + * @since 1.8.0 */ public function checkWalledGarden() { global $CONFIG; - if ($CONFIG->walled_garden && !isloggedin()) { - // hook into the index system call at the highest priority - register_plugin_hook('index', 'system', 'elgg_walled_garden_index', 1); + // command line calls should not invoke the walled garden check + if (PHP_SAPI === 'cli') { + return; + } - if (!$this->isPublicPage()) { - register_error(elgg_echo('loggedinrequired')); - forward(); + if ($CONFIG->walled_garden) { + if ($CONFIG->default_access == ACCESS_PUBLIC) { + $CONFIG->default_access = ACCESS_LOGGED_IN; + } + elgg_register_plugin_hook_handler( + 'access:collections:write', + 'user', + '_elgg_walled_garden_remove_public_access'); + + if (!elgg_is_logged_in()) { + // hook into the index system call at the highest priority + elgg_register_plugin_hook_handler('index', 'system', 'elgg_walled_garden_index', 1); + + if (!$this->isPublicPage()) { + if (!elgg_is_xhr()) { + $_SESSION['last_forward_from'] = current_page_url(); + } + register_error(elgg_echo('loggedinrequired')); + forward(); + } } } } @@ -338,6 +395,7 @@ class ElggSite extends ElggEntity { * @param string $url Defaults to the current URL. * * @return bool + * @since 1.8.0 */ public function isPublicPage($url = '') { global $CONFIG; @@ -358,23 +416,30 @@ class ElggSite extends ElggEntity { // default public pages $defaults = array( + 'walled_garden/.*', + 'login', 'action/login', - 'pg/register', + 'register', 'action/register', - 'pages/account/forgotten_password\.php', + 'forgotpassword', + 'resetpassword', 'action/user/requestnewpassword', - 'pg/resetpassword', + 'action/user/passwordreset', + 'action/security/refreshtoken', + 'ajax/view/js/languages', 'upgrade\.php', 'xml-rpc\.php', 'mt/mt-xmlrpc\.cgi', - '_css/css\.css', - '_css/js\.php', + 'css/.*', + 'js/.*', + 'cache/css/.*', + 'cache/js/.*', + 'cron/.*', + 'services/.*', ); // include a hook for plugin authors to include public pages - $plugins = trigger_plugin_hook('public_pages', 'walled_garden', NULL, array()); - - // lookup admin-specific public pages + $plugins = elgg_trigger_plugin_hook('public_pages', 'walled_garden', NULL, array()); // allow public pages foreach (array_merge($defaults, $plugins) as $public) { diff --git a/engine/classes/ElggStaticVariableCache.php b/engine/classes/ElggStaticVariableCache.php index a846ab60f..9c14fdfba 100644 --- a/engine/classes/ElggStaticVariableCache.php +++ b/engine/classes/ElggStaticVariableCache.php @@ -11,7 +11,7 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache { /** * The cache. * - * @var unknown_type + * @var array */ private static $__cache; @@ -21,8 +21,8 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache { * This function creates a variable cache in a static variable in * memory, optionally with a given namespace (to avoid overlap). * - * @param string $namespace The namespace for this cache to write to - * note, namespaces of the same name are shared! + * @param string $namespace The namespace for this cache to write to. + * @warning namespaces of the same name are shared! */ function __construct($namespace = 'default') { $this->setNamespace($namespace); @@ -80,7 +80,7 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache { } /** - * This was probably meant to delete everything? + * Clears the cache for a particular namespace * * @return void */ @@ -93,4 +93,4 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache { ElggStaticVariableCache::$__cache[$namespace] = array(); } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggTranslit.php b/engine/classes/ElggTranslit.php new file mode 100644 index 000000000..b4bf87797 --- /dev/null +++ b/engine/classes/ElggTranslit.php @@ -0,0 +1,269 @@ +<?php +/** + * Elgg Transliterate + * + * For creating "friendly titles" for URLs + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. For more information, see + * <http://www.doctrine-project.org>. + * + * @package Elgg.Core + * @author Konsta Vesterinen <kvesteri@cc.hut.fi> + * @author Jonathan H. Wage <jonwage@gmail.com> + * @author Steve Clay <steve@mrclay.org> + * + * @access private Plugin authors should not use this directly + */ +class ElggTranslit { + + /** + * Create a version of a string for embedding in a URL + * + * @param string $string A UTF-8 string + * @param string $separator The character to separate words with + * @return string + */ + static public function urlize($string, $separator = '-') { + // Iñtërnâtiônàlizætiøn, AND 日本語! + + // try to force combined chars because the translit map and others expect it + if (self::hasNormalizerSupport()) { + $nfc = normalizer_normalize($string); + if (is_string($nfc)) { + $string = $nfc; + } + } + // Internationalization, AND 日本語! + $string = self::transliterateAscii($string); + + // allow HTML tags in titles + $string = preg_replace('~<([a-zA-Z][^>]*)>~', ' $1 ', $string); + + // more substitutions + // @todo put these somewhere else + $string = strtr($string, array( + // currency + "\xE2\x82\xAC" /* € */ => ' E ', + "\xC2\xA3" /* £ */ => ' GBP ', + )); + + // remove all ASCII except 0-9a-zA-Z, hyphen, underscore, and whitespace + // note: "x" modifier did not work with this pattern. + $string = preg_replace('~[' + . '\x00-\x08' // control chars + . '\x0b\x0c' // vert tab, form feed + . '\x0e-\x1f' // control chars + . '\x21-\x2c' // ! ... , + . '\x2e\x2f' // . slash + . '\x3a-\x40' // : ... @ + . '\x5b-\x5e' // [ ... ^ + . '\x60' // ` + . '\x7b-\x7f' // { ... DEL + . ']~', '', $string); + $string = strtr($string, '', ''); + + // internationalization, and 日本語! + // note: not using elgg_strtolower to keep this class portable + $string = is_callable('mb_strtolower') + ? mb_strtolower($string, 'UTF-8') + : strtolower($string); + + // split by ASCII chars not in 0-9a-zA-Z + // note: we cannot use [^0-9a-zA-Z] because that matches multibyte chars. + // note: "x" modifier did not work with this pattern. + $pattern = '~[' + . '\x00-\x2f' // controls ... slash + . '\x3a-\x40' // : ... @ + . '\x5b-\x60' // [ ... ` + . '\x7b-\x7f' // { ... DEL + . ']+~x'; + + // ['internationalization', 'and', '日本語'] + $words = preg_split($pattern, $string, -1, PREG_SPLIT_NO_EMPTY); + + // ['internationalization', 'and', '%E6%97%A5%E6%9C%AC%E8%AA%9E'] + $words = array_map('urlencode', $words); + + // internationalization-and-%E6%97%A5%E6%9C%AC%E8%AA%9E + return implode($separator, $words); + } + + /** + * Transliterate Western multibyte chars to ASCII + * + * @param string $utf8 a UTF-8 string + * @return string + */ + static public function transliterateAscii($utf8) { + static $map = null; + if (!preg_match('/[\x80-\xff]/', $utf8)) { + return $utf8; + } + if (null === $map) { + $map = self::getAsciiTranslitMap(); + } + return strtr($utf8, $map); + } + + /** + * Get array of UTF-8 (NFC) character replacements. + * + * @return array + */ + static public function getAsciiTranslitMap() { + return array( + // Decompositions for Latin-1 Supplement + "\xC2\xAA" /* ª */ => 'a', "\xC2\xBA" /* º */ => 'o', "\xC3\x80" /* À */ => 'A', + "\xC3\x81" /* Á */ => 'A', "\xC3\x82" /*  */ => 'A', "\xC3\x83" /* à */ => 'A', + "\xC3\x84" /* Ä */ => 'A', "\xC3\x85" /* Å */ => 'A', "\xC3\x86" /* Æ */ => 'AE', + "\xC3\x87" /* Ç */ => 'C', "\xC3\x88" /* È */ => 'E', "\xC3\x89" /* É */ => 'E', + "\xC3\x8A" /* Ê */ => 'E', "\xC3\x8B" /* Ë */ => 'E', "\xC3\x8C" /* Ì */ => 'I', + "\xC3\x8D" /* Í */ => 'I', "\xC3\x8E" /* Î */ => 'I', "\xC3\x8F" /* Ï */ => 'I', + "\xC3\x90" /* Ð */ => 'D', "\xC3\x91" /* Ñ */ => 'N', "\xC3\x92" /* Ò */ => 'O', + "\xC3\x93" /* Ó */ => 'O', "\xC3\x94" /* Ô */ => 'O', "\xC3\x95" /* Õ */ => 'O', + "\xC3\x96" /* Ö */ => 'O', "\xC3\x99" /* Ù */ => 'U', "\xC3\x9A" /* Ú */ => 'U', + "\xC3\x9B" /* Û */ => 'U', "\xC3\x9C" /* Ü */ => 'U', "\xC3\x9D" /* Ý */ => 'Y', + "\xC3\x9E" /* Þ */ => 'TH', "\xC3\x9F" /* ß */ => 'ss', "\xC3\xA0" /* à */ => 'a', + "\xC3\xA1" /* á */ => 'a', "\xC3\xA2" /* â */ => 'a', "\xC3\xA3" /* ã */ => 'a', + "\xC3\xA4" /* ä */ => 'a', "\xC3\xA5" /* å */ => 'a', "\xC3\xA6" /* æ */ => 'ae', + "\xC3\xA7" /* ç */ => 'c', "\xC3\xA8" /* è */ => 'e', "\xC3\xA9" /* é */ => 'e', + "\xC3\xAA" /* ê */ => 'e', "\xC3\xAB" /* ë */ => 'e', "\xC3\xAC" /* ì */ => 'i', + "\xC3\xAD" /* í */ => 'i', "\xC3\xAE" /* î */ => 'i', "\xC3\xAF" /* ï */ => 'i', + "\xC3\xB0" /* ð */ => 'd', "\xC3\xB1" /* ñ */ => 'n', "\xC3\xB2" /* ò */ => 'o', + "\xC3\xB3" /* ó */ => 'o', "\xC3\xB4" /* ô */ => 'o', "\xC3\xB5" /* õ */ => 'o', + "\xC3\xB6" /* ö */ => 'o', "\xC3\xB8" /* ø */ => 'o', "\xC3\xB9" /* ù */ => 'u', + "\xC3\xBA" /* ú */ => 'u', "\xC3\xBB" /* û */ => 'u', "\xC3\xBC" /* ü */ => 'u', + "\xC3\xBD" /* ý */ => 'y', "\xC3\xBE" /* þ */ => 'th', "\xC3\xBF" /* ÿ */ => 'y', + "\xC3\x98" /* Ø */ => 'O', + // Decompositions for Latin Extended-A + "\xC4\x80" /* Ā */ => 'A', "\xC4\x81" /* ā */ => 'a', "\xC4\x82" /* Ă */ => 'A', + "\xC4\x83" /* ă */ => 'a', "\xC4\x84" /* Ą */ => 'A', "\xC4\x85" /* ą */ => 'a', + "\xC4\x86" /* Ć */ => 'C', "\xC4\x87" /* ć */ => 'c', "\xC4\x88" /* Ĉ */ => 'C', + "\xC4\x89" /* ĉ */ => 'c', "\xC4\x8A" /* Ċ */ => 'C', "\xC4\x8B" /* ċ */ => 'c', + "\xC4\x8C" /* Č */ => 'C', "\xC4\x8D" /* č */ => 'c', "\xC4\x8E" /* Ď */ => 'D', + "\xC4\x8F" /* ď */ => 'd', "\xC4\x90" /* Đ */ => 'D', "\xC4\x91" /* đ */ => 'd', + "\xC4\x92" /* Ē */ => 'E', "\xC4\x93" /* ē */ => 'e', "\xC4\x94" /* Ĕ */ => 'E', + "\xC4\x95" /* ĕ */ => 'e', "\xC4\x96" /* Ė */ => 'E', "\xC4\x97" /* ė */ => 'e', + "\xC4\x98" /* Ę */ => 'E', "\xC4\x99" /* ę */ => 'e', "\xC4\x9A" /* Ě */ => 'E', + "\xC4\x9B" /* ě */ => 'e', "\xC4\x9C" /* Ĝ */ => 'G', "\xC4\x9D" /* ĝ */ => 'g', + "\xC4\x9E" /* Ğ */ => 'G', "\xC4\x9F" /* ğ */ => 'g', "\xC4\xA0" /* Ġ */ => 'G', + "\xC4\xA1" /* ġ */ => 'g', "\xC4\xA2" /* Ģ */ => 'G', "\xC4\xA3" /* ģ */ => 'g', + "\xC4\xA4" /* Ĥ */ => 'H', "\xC4\xA5" /* ĥ */ => 'h', "\xC4\xA6" /* Ħ */ => 'H', + "\xC4\xA7" /* ħ */ => 'h', "\xC4\xA8" /* Ĩ */ => 'I', "\xC4\xA9" /* ĩ */ => 'i', + "\xC4\xAA" /* Ī */ => 'I', "\xC4\xAB" /* ī */ => 'i', "\xC4\xAC" /* Ĭ */ => 'I', + "\xC4\xAD" /* ĭ */ => 'i', "\xC4\xAE" /* Į */ => 'I', "\xC4\xAF" /* į */ => 'i', + "\xC4\xB0" /* İ */ => 'I', "\xC4\xB1" /* ı */ => 'i', "\xC4\xB2" /* IJ */ => 'IJ', + "\xC4\xB3" /* ij */ => 'ij', "\xC4\xB4" /* Ĵ */ => 'J', "\xC4\xB5" /* ĵ */ => 'j', + "\xC4\xB6" /* Ķ */ => 'K', "\xC4\xB7" /* ķ */ => 'k', "\xC4\xB8" /* ĸ */ => 'k', + "\xC4\xB9" /* Ĺ */ => 'L', "\xC4\xBA" /* ĺ */ => 'l', "\xC4\xBB" /* Ļ */ => 'L', + "\xC4\xBC" /* ļ */ => 'l', "\xC4\xBD" /* Ľ */ => 'L', "\xC4\xBE" /* ľ */ => 'l', + "\xC4\xBF" /* Ŀ */ => 'L', "\xC5\x80" /* ŀ */ => 'l', "\xC5\x81" /* Ł */ => 'L', + "\xC5\x82" /* ł */ => 'l', "\xC5\x83" /* Ń */ => 'N', "\xC5\x84" /* ń */ => 'n', + "\xC5\x85" /* Ņ */ => 'N', "\xC5\x86" /* ņ */ => 'n', "\xC5\x87" /* Ň */ => 'N', + "\xC5\x88" /* ň */ => 'n', "\xC5\x89" /* ʼn */ => 'N', "\xC5\x8A" /* Ŋ */ => 'n', + "\xC5\x8B" /* ŋ */ => 'N', "\xC5\x8C" /* Ō */ => 'O', "\xC5\x8D" /* ō */ => 'o', + "\xC5\x8E" /* Ŏ */ => 'O', "\xC5\x8F" /* ŏ */ => 'o', "\xC5\x90" /* Ő */ => 'O', + "\xC5\x91" /* ő */ => 'o', "\xC5\x92" /* Œ */ => 'OE', "\xC5\x93" /* œ */ => 'oe', + "\xC5\x94" /* Ŕ */ => 'R', "\xC5\x95" /* ŕ */ => 'r', "\xC5\x96" /* Ŗ */ => 'R', + "\xC5\x97" /* ŗ */ => 'r', "\xC5\x98" /* Ř */ => 'R', "\xC5\x99" /* ř */ => 'r', + "\xC5\x9A" /* Ś */ => 'S', "\xC5\x9B" /* ś */ => 's', "\xC5\x9C" /* Ŝ */ => 'S', + "\xC5\x9D" /* ŝ */ => 's', "\xC5\x9E" /* Ş */ => 'S', "\xC5\x9F" /* ş */ => 's', + "\xC5\xA0" /* Š */ => 'S', "\xC5\xA1" /* š */ => 's', "\xC5\xA2" /* Ţ */ => 'T', + "\xC5\xA3" /* ţ */ => 't', "\xC5\xA4" /* Ť */ => 'T', "\xC5\xA5" /* ť */ => 't', + "\xC5\xA6" /* Ŧ */ => 'T', "\xC5\xA7" /* ŧ */ => 't', "\xC5\xA8" /* Ũ */ => 'U', + "\xC5\xA9" /* ũ */ => 'u', "\xC5\xAA" /* Ū */ => 'U', "\xC5\xAB" /* ū */ => 'u', + "\xC5\xAC" /* Ŭ */ => 'U', "\xC5\xAD" /* ŭ */ => 'u', "\xC5\xAE" /* Ů */ => 'U', + "\xC5\xAF" /* ů */ => 'u', "\xC5\xB0" /* Ű */ => 'U', "\xC5\xB1" /* ű */ => 'u', + "\xC5\xB2" /* Ų */ => 'U', "\xC5\xB3" /* ų */ => 'u', "\xC5\xB4" /* Ŵ */ => 'W', + "\xC5\xB5" /* ŵ */ => 'w', "\xC5\xB6" /* Ŷ */ => 'Y', "\xC5\xB7" /* ŷ */ => 'y', + "\xC5\xB8" /* Ÿ */ => 'Y', "\xC5\xB9" /* Ź */ => 'Z', "\xC5\xBA" /* ź */ => 'z', + "\xC5\xBB" /* Ż */ => 'Z', "\xC5\xBC" /* ż */ => 'z', "\xC5\xBD" /* Ž */ => 'Z', + "\xC5\xBE" /* ž */ => 'z', "\xC5\xBF" /* ſ */ => 's', + // Decompositions for Latin Extended-B + "\xC8\x98" /* Ș */ => 'S', "\xC8\x99" /* ș */ => 's', + "\xC8\x9A" /* Ț */ => 'T', "\xC8\x9B" /* ț */ => 't', + // unmarked + "\xC6\xA0" /* Ơ */ => 'O', "\xC6\xA1" /* ơ */ => 'o', + "\xC6\xAF" /* Ư */ => 'U', "\xC6\xB0" /* ư */ => 'u', + // grave accent + "\xE1\xBA\xA6" /* Ầ */ => 'A', "\xE1\xBA\xA7" /* ầ */ => 'a', + "\xE1\xBA\xB0" /* Ằ */ => 'A', "\xE1\xBA\xB1" /* ằ */ => 'a', + "\xE1\xBB\x80" /* Ề */ => 'E', "\xE1\xBB\x81" /* ề */ => 'e', + "\xE1\xBB\x92" /* Ồ */ => 'O', "\xE1\xBB\x93" /* ồ */ => 'o', + "\xE1\xBB\x9C" /* Ờ */ => 'O', "\xE1\xBB\x9D" /* ờ */ => 'o', + "\xE1\xBB\xAA" /* Ừ */ => 'U', "\xE1\xBB\xAB" /* ừ */ => 'u', + "\xE1\xBB\xB2" /* Ỳ */ => 'Y', "\xE1\xBB\xB3" /* ỳ */ => 'y', + // hook + "\xE1\xBA\xA2" /* Ả */ => 'A', "\xE1\xBA\xA3" /* ả */ => 'a', + "\xE1\xBA\xA8" /* Ẩ */ => 'A', "\xE1\xBA\xA9" /* ẩ */ => 'a', + "\xE1\xBA\xB2" /* Ẳ */ => 'A', "\xE1\xBA\xB3" /* ẳ */ => 'a', + "\xE1\xBA\xBA" /* Ẻ */ => 'E', "\xE1\xBA\xBB" /* ẻ */ => 'e', + "\xE1\xBB\x82" /* Ể */ => 'E', "\xE1\xBB\x83" /* ể */ => 'e', + "\xE1\xBB\x88" /* Ỉ */ => 'I', "\xE1\xBB\x89" /* ỉ */ => 'i', + "\xE1\xBB\x8E" /* Ỏ */ => 'O', "\xE1\xBB\x8F" /* ỏ */ => 'o', + "\xE1\xBB\x94" /* Ổ */ => 'O', "\xE1\xBB\x95" /* ổ */ => 'o', + "\xE1\xBB\x9E" /* Ở */ => 'O', "\xE1\xBB\x9F" /* ở */ => 'o', + "\xE1\xBB\xA6" /* Ủ */ => 'U', "\xE1\xBB\xA7" /* ủ */ => 'u', + "\xE1\xBB\xAC" /* Ử */ => 'U', "\xE1\xBB\xAD" /* ử */ => 'u', + "\xE1\xBB\xB6" /* Ỷ */ => 'Y', "\xE1\xBB\xB7" /* ỷ */ => 'y', + // tilde + "\xE1\xBA\xAA" /* Ẫ */ => 'A', "\xE1\xBA\xAB" /* ẫ */ => 'a', + "\xE1\xBA\xB4" /* Ẵ */ => 'A', "\xE1\xBA\xB5" /* ẵ */ => 'a', + "\xE1\xBA\xBC" /* Ẽ */ => 'E', "\xE1\xBA\xBD" /* ẽ */ => 'e', + "\xE1\xBB\x84" /* Ễ */ => 'E', "\xE1\xBB\x85" /* ễ */ => 'e', + "\xE1\xBB\x96" /* Ỗ */ => 'O', "\xE1\xBB\x97" /* ỗ */ => 'o', + "\xE1\xBB\xA0" /* Ỡ */ => 'O', "\xE1\xBB\xA1" /* ỡ */ => 'o', + "\xE1\xBB\xAE" /* Ữ */ => 'U', "\xE1\xBB\xAF" /* ữ */ => 'u', + "\xE1\xBB\xB8" /* Ỹ */ => 'Y', "\xE1\xBB\xB9" /* ỹ */ => 'y', + // acute accent + "\xE1\xBA\xA4" /* Ấ */ => 'A', "\xE1\xBA\xA5" /* ấ */ => 'a', + "\xE1\xBA\xAE" /* Ắ */ => 'A', "\xE1\xBA\xAF" /* ắ */ => 'a', + "\xE1\xBA\xBE" /* Ế */ => 'E', "\xE1\xBA\xBF" /* ế */ => 'e', + "\xE1\xBB\x90" /* Ố */ => 'O', "\xE1\xBB\x91" /* ố */ => 'o', + "\xE1\xBB\x9A" /* Ớ */ => 'O', "\xE1\xBB\x9B" /* ớ */ => 'o', + "\xE1\xBB\xA8" /* Ứ */ => 'U', "\xE1\xBB\xA9" /* ứ */ => 'u', + // dot below + "\xE1\xBA\xA0" /* Ạ */ => 'A', "\xE1\xBA\xA1" /* ạ */ => 'a', + "\xE1\xBA\xAC" /* Ậ */ => 'A', "\xE1\xBA\xAD" /* ậ */ => 'a', + "\xE1\xBA\xB6" /* Ặ */ => 'A', "\xE1\xBA\xB7" /* ặ */ => 'a', + "\xE1\xBA\xB8" /* Ẹ */ => 'E', "\xE1\xBA\xB9" /* ẹ */ => 'e', + "\xE1\xBB\x86" /* Ệ */ => 'E', "\xE1\xBB\x87" /* ệ */ => 'e', + "\xE1\xBB\x8A" /* Ị */ => 'I', "\xE1\xBB\x8B" /* ị */ => 'i', + "\xE1\xBB\x8C" /* Ọ */ => 'O', "\xE1\xBB\x8D" /* ọ */ => 'o', + "\xE1\xBB\x98" /* Ộ */ => 'O', "\xE1\xBB\x99" /* ộ */ => 'o', + "\xE1\xBB\xA2" /* Ợ */ => 'O', "\xE1\xBB\xA3" /* ợ */ => 'o', + "\xE1\xBB\xA4" /* Ụ */ => 'U', "\xE1\xBB\xA5" /* ụ */ => 'u', + "\xE1\xBB\xB0" /* Ự */ => 'U', "\xE1\xBB\xB1" /* ự */ => 'u', + "\xE1\xBB\xB4" /* Ỵ */ => 'Y', "\xE1\xBB\xB5" /* ỵ */ => 'y', + ); + } + + /** + * Tests that "normalizer_normalize" exists and works + * + * @return bool + */ + static public function hasNormalizerSupport() { + static $ret = null; + if (null === $ret) { + $form_c = "\xC3\x85"; // 'LATIN CAPITAL LETTER A WITH RING ABOVE' (U+00C5) + $form_d = "A\xCC\x8A"; // A followed by 'COMBINING RING ABOVE' (U+030A) + $ret = (function_exists('normalizer_normalize') + && $form_c === normalizer_normalize($form_d)); + } + return $ret; + } +} diff --git a/engine/classes/ElggUser.php b/engine/classes/ElggUser.php index ec951b359..6163f9b62 100644 --- a/engine/classes/ElggUser.php +++ b/engine/classes/ElggUser.php @@ -6,26 +6,20 @@ * * @package Elgg.Core * @subpackage DataModel.User + * + * @property string $name The display name that the user will be known by in the network + * @property string $username The short, reference name for the user in the network + * @property string $email The email address to which Elgg will send email notifications + * @property string $language The language preference of the user (ISO 639-1 formatted) + * @property string $banned 'yes' if the user is banned from the network, 'no' otherwise + * @property string $admin 'yes' if the user is an administrator of the network, 'no' otherwise + * @property string $password The hashed password of the user + * @property string $salt The salt used to secure the password before hashing */ class ElggUser extends ElggEntity implements Friendable { - /** - * Initialise the attributes array. - * This is vital to distinguish between metadata and base parameters. - * - * Place your base parameters here. - * - * @deprecated 1.8 Use ElggUser::initializeAttributes() - * - * @return void - */ - protected function initialise_attributes() { - elgg_deprecated_notice('ElggUser::initialise_attributes() is deprecated by ::initializeAttributes()', 1.8); - return $this->initializeAttributes(); - } - - /** + /** * Initialise the attributes array. * This is vital to distinguish between metadata and base parameters. * @@ -37,15 +31,18 @@ class ElggUser extends ElggEntity parent::initializeAttributes(); $this->attributes['type'] = "user"; - $this->attributes['name'] = ""; - $this->attributes['username'] = ""; - $this->attributes['password'] = ""; - $this->attributes['salt'] = ""; - $this->attributes['email'] = ""; - $this->attributes['language'] = ""; - $this->attributes['code'] = ""; + $this->attributes['name'] = NULL; + $this->attributes['username'] = NULL; + $this->attributes['password'] = NULL; + $this->attributes['salt'] = NULL; + $this->attributes['email'] = NULL; + $this->attributes['language'] = NULL; + $this->attributes['code'] = NULL; $this->attributes['banned'] = "no"; $this->attributes['admin'] = 'no'; + $this->attributes['prev_last_action'] = NULL; + $this->attributes['last_login'] = NULL; + $this->attributes['prev_last_login'] = NULL; $this->attributes['tables_split'] = 2; } @@ -53,45 +50,46 @@ class ElggUser extends ElggEntity * Construct a new user entity, optionally from a given id value. * * @param mixed $guid If an int, load that GUID. - * If a db row then will attempt to load the rest of the data. + * If an entity table db row then will load the rest of the data. * * @throws Exception if there was a problem creating the user. */ function __construct($guid = null) { $this->initializeAttributes(); + // compatibility for 1.7 api. + $this->initialise_attributes(false); + if (!empty($guid)) { - // Is $guid is a DB row - either a entity row, or a user table row. + // Is $guid is a DB entity row if ($guid instanceof stdClass) { // Load the rest - if (!$this->load($guid->guid)) { - $msg = sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid); + if (!$this->load($guid)) { + $msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid)); throw new IOException($msg); } - - // See if this is a username } else if (is_string($guid)) { - $guid = get_user_by_username($guid); - foreach ($guid->attributes as $key => $value) { - $this->attributes[$key] = $value; + // $guid is a username + $user = get_user_by_username($guid); + if ($user) { + foreach ($user->attributes as $key => $value) { + $this->attributes[$key] = $value; + } } - - // Is $guid is an ElggUser? Use a copy constructor } else if ($guid instanceof ElggUser) { + // $guid is an ElggUser so this is a copy constructor elgg_deprecated_notice('This type of usage of the ElggUser constructor was deprecated. Please use the clone method.', 1.7); foreach ($guid->attributes as $key => $value) { $this->attributes[$key] = $value; } - - // Is this is an ElggEntity but not an ElggUser = ERROR! } else if ($guid instanceof ElggEntity) { + // @todo why have a special case here throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggUser')); - - // We assume if we have got this far, $guid is an int } else if (is_numeric($guid)) { + // $guid is a GUID so load entity if (!$this->load($guid)) { - throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid)); + throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid))); } } else { throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); @@ -100,38 +98,24 @@ class ElggUser extends ElggEntity } /** - * Override the load function. - * This function will ensure that all data is loaded (were possible), so - * if only part of the ElggUser is loaded, it'll load the rest. + * Load the ElggUser data from the database * - * @param int $guid ElggUser GUID + * @param mixed $guid ElggUser GUID or stdClass database row from entity table * - * @return true|false + * @return bool */ protected function load($guid) { - // Test to see if we have the generic stuff - if (!parent::load($guid)) { - return false; - } + $attr_loader = new ElggAttributeLoader(get_class(), 'user', $this->attributes); + $attr_loader->secondary_loader = 'get_user_entity_as_row'; - // Check the type - if ($this->attributes['type'] != 'user') { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class()); - throw new InvalidClassException($msg); - } - - // Load missing data - $row = get_user_entity_as_row($guid); - if (($row) && (!$this->isFullyLoaded())) { - // If $row isn't a cached copy then increment the counter - $this->attributes['tables_loaded'] ++; + $attrs = $attr_loader->getRequiredAttributes($guid); + if (!$attrs) { + return false; } - // Now put these into the attributes array as core values - $objarray = (array) $row; - foreach ($objarray as $key => $value) { - $this->attributes[$key] = $value; - } + $this->attributes = $attrs; + $this->attributes['tables_loaded'] = 2; + _elgg_cache_entity($this); return true; } @@ -139,7 +123,7 @@ class ElggUser extends ElggEntity /** * Saves this user to the database. * - * @return true|false + * @return bool */ public function save() { // Save generic stuff @@ -148,9 +132,13 @@ class ElggUser extends ElggEntity } // Now save specific stuff - return create_user_entity($this->get('guid'), $this->get('name'), $this->get('username'), + _elgg_disable_caching_for_entity($this->guid); + $ret = create_user_entity($this->get('guid'), $this->get('name'), $this->get('username'), $this->get('password'), $this->get('salt'), $this->get('email'), $this->get('language'), $this->get('code')); + _elgg_enable_caching_for_entity($this->guid); + + return $ret; } /** @@ -169,9 +157,6 @@ class ElggUser extends ElggEntity unset($CODE_TO_GUID_MAP_CACHE[$this->code]); } - // Delete owned data - clear_annotations_by_owner($this->guid); - clear_metadata_by_owner($this->guid); clear_user_files($this); // Delete entity @@ -262,7 +247,7 @@ class ElggUser extends ElggEntity * @param int $limit The number of results to return * @param int $offset Any indexing offset * - * @return bool + * @return array */ function getSites($subtype = "", $limit = 10, $offset = 0) { return get_user_sites($this->getGUID(), $subtype, $limit, $offset); @@ -273,7 +258,7 @@ class ElggUser extends ElggEntity * * @param int $site_guid The guid of the site to add it to * - * @return true|false + * @return bool */ function addToSite($site_guid) { return add_site_user($site_guid, $this->getGUID()); @@ -284,29 +269,29 @@ class ElggUser extends ElggEntity * * @param int $site_guid The guid of the site to remove it from * - * @return true|false + * @return bool */ function removeFromSite($site_guid) { return remove_site_user($site_guid, $this->getGUID()); } /** - * Adds a user to this user's friends list + * Adds a user as a friend * * @param int $friend_guid The GUID of the user to add * - * @return true|false Depending on success + * @return bool */ function addFriend($friend_guid) { return user_add_friend($this->getGUID(), $friend_guid); } /** - * Removes a user from this user's friends list + * Removes a user as a friend * * @param int $friend_guid The GUID of the user to remove * - * @return true|false Depending on success + * @return bool */ function removeFriend($friend_guid) { return user_remove_friend($this->getGUID(), $friend_guid); @@ -315,62 +300,141 @@ class ElggUser extends ElggEntity /** * Determines whether or not this user is a friend of the currently logged in user * - * @return true|false + * @return bool */ function isFriend() { - return user_is_friend(get_loggedin_userid(), $this->getGUID()); + return $this->isFriendOf(elgg_get_logged_in_user_guid()); } /** * Determines whether this user is friends with another user * - * @param int $user_guid The GUID of the user to check is on this user's friends list + * @param int $user_guid The GUID of the user to check against * - * @return true|false + * @return bool */ function isFriendsWith($user_guid) { return user_is_friend($this->getGUID(), $user_guid); } /** - * Determines whether or not this user is on another user's friends list + * Determines whether or not this user is another user's friend * * @param int $user_guid The GUID of the user to check against * - * @return true|false + * @return bool */ function isFriendOf($user_guid) { return user_is_friend($user_guid, $this->getGUID()); } /** - * Retrieves a list of this user's friends + * Gets this user's friends * - * @param string $subtype Optionally, the subtype of user to filter to (leave blank for all) + * @param string $subtype Optionally, the user subtype (leave blank for all) * @param int $limit The number of users to retrieve * @param int $offset Indexing offset, if any * - * @return array|false Array of ElggUsers, or false, depending on success + * @return array|false Array of ElggUser, or false, depending on success */ function getFriends($subtype = "", $limit = 10, $offset = 0) { return get_user_friends($this->getGUID(), $subtype, $limit, $offset); } /** - * Retrieves a list of people who have made this user a friend + * Gets users who have made this user a friend * - * @param string $subtype Optionally, the subtype of user to filter to (leave blank for all) + * @param string $subtype Optionally, the user subtype (leave blank for all) * @param int $limit The number of users to retrieve * @param int $offset Indexing offset, if any * - * @return array|false Array of ElggUsers, or false, depending on success + * @return array|false Array of ElggUser, or false, depending on success */ function getFriendsOf($subtype = "", $limit = 10, $offset = 0) { return get_user_friends_of($this->getGUID(), $subtype, $limit, $offset); } /** - * Get an array of ElggObjects owned by this user. + * Lists the user's friends + * + * @param string $subtype Optionally, the user subtype (leave blank for all) + * @param int $limit The number of users to retrieve + * @param array $vars Display variables for the user view + * + * @return string Rendered list of friends + * @since 1.8.0 + */ + function listFriends($subtype = "", $limit = 10, array $vars = array()) { + $defaults = array( + 'type' => 'user', + 'relationship' => 'friend', + 'relationship_guid' => $this->guid, + 'limit' => $limit, + 'full_view' => false, + ); + + $options = array_merge($defaults, $vars); + + if ($subtype) { + $options['subtype'] = $subtype; + } + + return elgg_list_entities_from_relationship($options); + } + + /** + * Gets the user's groups + * + * @param string $subtype Optionally, the subtype of user to filter to (leave blank for all) + * @param int $limit The number of groups to retrieve + * @param int $offset Indexing offset, if any + * + * @return array|false Array of ElggGroup, or false, depending on success + */ + function getGroups($subtype = "", $limit = 10, $offset = 0) { + $options = array( + 'type' => 'group', + 'relationship' => 'member', + 'relationship_guid' => $this->guid, + 'limit' => $limit, + 'offset' => $offset, + ); + + if ($subtype) { + $options['subtype'] = $subtype; + } + + return elgg_get_entities_from_relationship($options); + } + + /** + * Lists the user's groups + * + * @param string $subtype Optionally, the user subtype (leave blank for all) + * @param int $limit The number of users to retrieve + * @param int $offset Indexing offset, if any + * + * @return string + */ + function listGroups($subtype = "", $limit = 10, $offset = 0) { + $options = array( + 'type' => 'group', + 'relationship' => 'member', + 'relationship_guid' => $this->guid, + 'limit' => $limit, + 'offset' => $offset, + 'full_view' => false, + ); + + if ($subtype) { + $options['subtype'] = $subtype; + } + + return elgg_list_entities_from_relationship($options); + } + + /** + * Get an array of ElggObject owned by this user. * * @param string $subtype The subtype of the objects, if any * @param int $limit Number of results to return @@ -379,7 +443,14 @@ class ElggUser extends ElggEntity * @return array|false */ public function getObjects($subtype = "", $limit = 10, $offset = 0) { - return get_user_objects($this->getGUID(), $subtype, $limit, $offset); + $params = array( + 'type' => 'object', + 'subtype' => $subtype, + 'owner_guid' => $this->getGUID(), + 'limit' => $limit, + 'offset' => $offset + ); + return elgg_get_entities($params); } /** @@ -416,22 +487,36 @@ class ElggUser extends ElggEntity * @return array|false */ public function getCollections($subtype = "", $limit = 10, $offset = 0) { - return get_user_collections($this->getGUID(), $subtype, $limit, $offset); + elgg_deprecated_notice("ElggUser::getCollections() has been deprecated", 1.8); + return false; } /** - * If a user's owner is blank, return its own GUID as the owner + * Get a user's owner GUID * - * @return int User GUID + * Returns it's own GUID if the user is not owned. + * + * @return int */ - function getOwner() { + function getOwnerGUID() { if ($this->owner_guid == 0) { - return $this->getGUID(); + return $this->guid; } return $this->owner_guid; } + /** + * If a user's owner is blank, return its own GUID as the owner + * + * @return int User GUID + * @deprecated 1.8 Use getOwnerGUID() + */ + function getOwner() { + elgg_deprecated_notice("ElggUser::getOwner deprecated for ElggUser::getOwnerGUID", 1.8); + $this->getOwnerGUID(); + } + // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// /** @@ -457,7 +542,7 @@ class ElggUser extends ElggEntity */ public function __set($name, $value) { if ($name == 'admin' || $name == 'siteadmin') { - elgg_deprecated_notice('The admin/siteadmin metadata are not longer used. Use ElggUser->makeAdmin() and ElggUser->removeAdmin().', '1.7.1'); + elgg_deprecated_notice('The admin/siteadmin metadata are not longer used. Use ElggUser->makeAdmin() and ElggUser->removeAdmin().', 1.7); if ($value == 'yes' || $value == '1') { $this->makeAdmin(); @@ -477,10 +562,27 @@ class ElggUser extends ElggEntity */ public function __get($name) { if ($name == 'admin' || $name == 'siteadmin') { - elgg_deprecated_notice('The admin/siteadmin metadata are not longer used. Use ElggUser->isAdmin().', '1.7.1'); + elgg_deprecated_notice('The admin/siteadmin metadata are not longer used. Use ElggUser->isAdmin().', 1.7); return $this->isAdmin(); } return parent::__get($name); } + + /** + * Can a user comment on this user? + * + * @see ElggEntity::canComment() + * + * @param int $user_guid User guid (default is logged in user) + * @return bool + * @since 1.8.0 + */ + public function canComment($user_guid = 0) { + $result = parent::canComment($user_guid); + if ($result !== null) { + return $result; + } + return false; + } } diff --git a/engine/classes/ElggVolatileMetadataCache.php b/engine/classes/ElggVolatileMetadataCache.php new file mode 100644 index 000000000..4acda7cee --- /dev/null +++ b/engine/classes/ElggVolatileMetadataCache.php @@ -0,0 +1,355 @@ +<?php +/** + * ElggVolatileMetadataCache + * In memory cache of known metadata values stored by entity. + * + * @package Elgg.Core + * @subpackage Cache + * + * @access private + */ +class ElggVolatileMetadataCache { + + /** + * The cached values (or null for known to be empty). If the portion of the cache + * is synchronized, missing values are assumed to indicate that values do not + * exist in storage, otherwise, we don't know what's there. + * + * @var array + */ + protected $values = array(); + + /** + * Does the cache know that it contains all names fetch-able from storage? + * The keys are entity GUIDs and either the value exists (true) or it's not set. + * + * @var array + */ + protected $isSynchronized = array(); + + /** + * @var null|bool + */ + protected $ignoreAccess = null; + + /** + * Cache metadata for an entity + * + * @param int $entity_guid The GUID of the entity + * @param array $values The metadata values to cache + * @return void + */ + public function saveAll($entity_guid, array $values) { + if (!$this->getIgnoreAccess()) { + $this->values[$entity_guid] = $values; + $this->isSynchronized[$entity_guid] = true; + } + } + + /** + * Get the metadata for an entity + * + * @param int $entity_guid The GUID of the entity + * @return array + */ + public function loadAll($entity_guid) { + if (isset($this->values[$entity_guid])) { + return $this->values[$entity_guid]; + } else { + return array(); + } + } + + /** + * Declare that there may be fetch-able metadata names in storage that this + * cache doesn't know about + * + * @param int $entity_guid The GUID of the entity + * @return void + */ + public function markOutOfSync($entity_guid) { + unset($this->isSynchronized[$entity_guid]); + } + + /** + * Have all the metadata for this entity been cached? + * + * @param int $entity_guid The GUID of the entity + * @return bool + */ + public function isSynchronized($entity_guid) { + return isset($this->isSynchronized[$entity_guid]); + } + + /** + * Cache a piece of metadata + * + * @param int $entity_guid The GUID of the entity + * @param string $name The metadata name + * @param array|int|string|null $value The metadata value. null means it is + * known that there is no fetch-able + * metadata under this name + * @param bool $allow_multiple Can the metadata be an array + * @return void + */ + public function save($entity_guid, $name, $value, $allow_multiple = false) { + if ($this->getIgnoreAccess()) { + // we don't know if what gets saves here will be available to user once + // access control returns, hence it's best to forget :/ + $this->markUnknown($entity_guid, $name); + } else { + if ($allow_multiple) { + if ($this->isKnown($entity_guid, $name)) { + $existing = $this->load($entity_guid, $name); + if ($existing !== null) { + $existing = (array) $existing; + $existing[] = $value; + $value = $existing; + } + } else { + // we don't know whether there are unknown values, so it's + // safest to leave that assumption + $this->markUnknown($entity_guid, $name); + return; + } + } + $this->values[$entity_guid][$name] = $value; + } + } + + /** + * Warning: You should always call isKnown() beforehand to verify that this + * function's return value should be trusted (otherwise a null return value + * is ambiguous). + * + * @param int $entity_guid The GUID of the entity + * @param string $name The metadata name + * @return array|string|int|null null = value does not exist + */ + public function load($entity_guid, $name) { + if (isset($this->values[$entity_guid]) && array_key_exists($name, $this->values[$entity_guid])) { + return $this->values[$entity_guid][$name]; + } else { + return null; + } + } + + /** + * Forget about this metadata entry. We don't want to try to guess what the + * next fetch from storage will return + * + * @param int $entity_guid The GUID of the entity + * @param string $name The metadata name + * @return void + */ + public function markUnknown($entity_guid, $name) { + unset($this->values[$entity_guid][$name]); + $this->markOutOfSync($entity_guid); + } + + /** + * If true, load() will return an accurate value for this name + * + * @param int $entity_guid The GUID of the entity + * @param string $name The metadata name + * @return bool + */ + public function isKnown($entity_guid, $name) { + if (isset($this->isSynchronized[$entity_guid])) { + return true; + } else { + return (isset($this->values[$entity_guid]) && array_key_exists($name, $this->values[$entity_guid])); + } + + } + + /** + * Declare that metadata under this name is known to be not fetch-able from storage + * + * @param int $entity_guid The GUID of the entity + * @param string $name The metadata name + * @return array + */ + public function markEmpty($entity_guid, $name) { + $this->values[$entity_guid][$name] = null; + } + + /** + * Forget about all metadata for an entity + * + * @param int $entity_guid The GUID of the entity + * @return void + */ + public function clear($entity_guid) { + $this->values[$entity_guid] = array(); + $this->markOutOfSync($entity_guid); + } + + /** + * Clear entire cache and mark all entities as out of sync + * + * @return void + */ + public function flush() { + $this->values = array(); + $this->isSynchronized = array(); + } + + /** + * Use this value instead of calling elgg_get_ignore_access(). By default that + * function will be called. + * + * This setting makes this component a little more loosely-coupled. + * + * @param bool $ignore Whether to ignore access or not + * @return void + */ + public function setIgnoreAccess($ignore) { + $this->ignoreAccess = (bool) $ignore; + } + + /** + * Tell the cache to call elgg_get_ignore_access() to determing access status. + * + * @return void + */ + public function unsetIgnoreAccess() { + $this->ignoreAccess = null; + } + + /** + * Get the ignore access value + * + * @return bool + */ + protected function getIgnoreAccess() { + if (null === $this->ignoreAccess) { + return elgg_get_ignore_access(); + } else { + return $this->ignoreAccess; + } + } + + /** + * Invalidate based on options passed to the global *_metadata functions + * + * @param string $action Action performed on metadata. "delete", "disable", or "enable" + * @param array $options Options passed to elgg_(delete|disable|enable)_metadata + * "guid" if given, invalidation will be limited to this entity + * "metadata_name" if given, invalidation will be limited to metadata with this name + * @return void + */ + public function invalidateByOptions($action, array $options) { + // remove as little as possible, optimizing for common cases + if (empty($options['guid'])) { + // safest to clear everything unless we want to make this even more complex :( + $this->flush(); + } else { + if (empty($options['metadata_name'])) { + // safest to clear the whole entity + $this->clear($options['guid']); + } else { + switch ($action) { + case 'delete': + $this->markEmpty($options['guid'], $options['metadata_name']); + break; + default: + $this->markUnknown($options['guid'], $options['metadata_name']); + } + } + } + } + + /** + * Populate the cache from a set of entities + * + * @param int|array $guids Array of or single GUIDs + * @return void + */ + public function populateFromEntities($guids) { + if (empty($guids)) { + return; + } + if (!is_array($guids)) { + $guids = array($guids); + } + $guids = array_unique($guids); + + // could be useful at some point in future + //$guids = $this->filterMetadataHeavyEntities($guids); + + $db_prefix = elgg_get_config('dbprefix'); + $options = array( + 'guids' => $guids, + 'limit' => 0, + 'callback' => false, + 'joins' => array( + "JOIN {$db_prefix}metastrings v ON n_table.value_id = v.id", + "JOIN {$db_prefix}metastrings n ON n_table.name_id = n.id", + ), + 'selects' => array('n.string AS name', 'v.string AS value'), + 'order_by' => 'n_table.entity_guid, n_table.time_created ASC', + + // @todo don't know why this is necessary + 'wheres' => array(get_access_sql_suffix('n_table')), + ); + $data = elgg_get_metadata($options); + + // build up metadata for each entity, save when GUID changes (or data ends) + $last_guid = null; + $metadata = array(); + $last_row_idx = count($data) - 1; + foreach ($data as $i => $row) { + $name = $row->name; + $value = ($row->value_type === 'text') ? $row->value : (int) $row->value; + $guid = $row->entity_guid; + if ($guid !== $last_guid) { + if ($last_guid) { + $this->saveAll($last_guid, $metadata); + } + $metadata = array(); + } + if (isset($metadata[$name])) { + $metadata[$name] = (array) $metadata[$name]; + $metadata[$name][] = $value; + } else { + $metadata[$name] = $value; + } + if (($i == $last_row_idx)) { + $this->saveAll($guid, $metadata); + } + $last_guid = $guid; + } + } + + /** + * Filter out entities whose concatenated metadata values (INTs casted as string) + * exceed a threshold in characters. This could be used to avoid overpopulating the + * cache if RAM usage becomes an issue. + * + * @param array $guids GUIDs of entities to examine + * @param int $limit Limit in characters of all metadata (with ints casted to strings) + * @return array + */ + public function filterMetadataHeavyEntities(array $guids, $limit = 1024000) { + $db_prefix = elgg_get_config('dbprefix'); + + $options = array( + 'guids' => $guids, + 'limit' => 0, + 'callback' => false, + 'joins' => "JOIN {$db_prefix}metastrings v ON n_table.value_id = v.id", + 'selects' => array('SUM(LENGTH(v.string)) AS bytes'), + 'order_by' => 'n_table.entity_guid, n_table.time_created ASC', + 'group_by' => 'n_table.entity_guid', + ); + $data = elgg_get_metadata($options); + // don't cache if metadata for entity is over 10MB (or rolled INT) + foreach ($data as $row) { + if ($row->bytes > $limit || $row->bytes < 0) { + array_splice($guids, array_search($row->entity_guid, $guids), 1); + } + } + return $guids; + } +} diff --git a/engine/classes/ElggWidget.php b/engine/classes/ElggWidget.php index c9a65967c..66191bf47 100644 --- a/engine/classes/ElggWidget.php +++ b/engine/classes/ElggWidget.php @@ -1,29 +1,23 @@ <?php /** - * Override ElggObject in order to store widget data in ultra-private stores. + * ElggWidget + * + * Stores metadata in private settings rather than as ElggMetadata * * @package Elgg.Core * @subpackage Widgets + * + * @property-read string $handler internal, do not use + * @property-read string $column internal, do not use + * @property-read string $order internal, do not use + * @property-read string $context internal, do not use */ class ElggWidget extends ElggObject { /** * Set subtype to widget. * - * @deprecated 1.8 use ElggWidget::initializeAttributes() - * - * @return void - */ - protected function initialise_attributes() { - elgg_deprecated_notice('ElggWidget::initialise_attributes() is deprecated by ::initializeAttributes()', 1.8); - - return $this->initializeAttributes(); - } - - /** - * Set subtype to widget. - * * @return void */ protected function initializeAttributes() { @@ -41,12 +35,12 @@ class ElggWidget extends ElggObject { */ public function get($name) { // See if its in our base attribute - if (isset($this->attributes[$name])) { + if (array_key_exists($name, $this->attributes)) { return $this->attributes[$name]; } // No, so see if its in the private data store. - $meta = get_private_setting($this->guid, $name); + $meta = $this->getPrivateSetting($name); if ($meta) { return $meta; } @@ -72,9 +66,180 @@ class ElggWidget extends ElggObject { $this->attributes[$name] = $value; } else { - return set_private_setting($this->guid, $name, $value); + return $this->setPrivateSetting($name, $value); + } + + return true; + } + + /** + * Set the widget context + * + * @param string $context The widget context + * @return bool + * @since 1.8.0 + */ + public function setContext($context) { + return $this->setPrivateSetting('context', $context); + } + + /** + * Get the widget context + * + * @return string + * @since 1.8.0 + */ + public function getContext() { + return $this->getPrivateSetting('context'); + } + + /** + * Get the title of the widget + * + * @return string + * @since 1.8.0 + */ + public function getTitle() { + $title = $this->title; + if (!$title) { + global $CONFIG; + $title = $CONFIG->widgets->handlers[$this->handler]->name; + } + return $title; + } + + /** + * Move the widget + * + * @param int $column The widget column + * @param int $rank Zero-based rank from the top of the column + * @return void + * @since 1.8.0 + */ + public function move($column, $rank) { + $options = array( + 'type' => 'object', + 'subtype' => 'widget', + 'container_guid' => $this->container_guid, + 'limit' => false, + 'private_setting_name_value_pairs' => array( + array('name' => 'context', 'value' => $this->getContext()), + array('name' => 'column', 'value' => $column) + ) + ); + $widgets = elgg_get_entities_from_private_settings($options); + if (!$widgets) { + $this->column = (int)$column; + $this->order = 0; + return; + } + + usort($widgets, create_function('$a,$b','return (int)$a->order > (int)$b->order;')); + + // remove widgets from inactive plugins + $widget_types = elgg_get_widget_types($this->context); + $inactive_widgets = array(); + foreach ($widgets as $index => $widget) { + if (!array_key_exists($widget->handler, $widget_types)) { + $inactive_widgets[] = $widget; + unset($widgets[$index]); + } + } + + $bottom_rank = count($widgets); + if ($column == $this->column) { + $bottom_rank--; + } + + if ($rank == 0) { + // top of the column + $this->order = reset($widgets)->order - 10; + } elseif ($rank == $bottom_rank) { + // bottom of the column of active widgets + $this->order = end($widgets)->order + 10; + } else { + // reorder widgets + + // remove the widget that's being moved from the array + foreach ($widgets as $index => $widget) { + if ($widget->guid == $this->guid) { + unset($widgets[$index]); + } + } + + // split the array in two and recombine with the moved widget in middle + $before = array_slice($widgets, 0, $rank); + array_push($before, $this); + $after = array_slice($widgets, $rank); + $widgets = array_merge($before, $after); + ksort($widgets); + $order = 0; + foreach ($widgets as $widget) { + $widget->order = $order; + $order += 10; + } + } + + // put inactive widgets at the bottom + if ($inactive_widgets) { + $bottom = 0; + foreach ($widgets as $widget) { + if ($widget->order > $bottom) { + $bottom = $widget->order; + } + } + $bottom += 10; + foreach ($inactive_widgets as $widget) { + $widget->order = $bottom; + $bottom += 10; + } + } + + $this->column = $column; + } + + /** + * Saves the widget's settings + * + * Plugins can override the save mechanism using the plugin hook: + * 'widget_settings', <widget handler identifier>. The widget and + * the parameters are passed. The plugin hook handler should return + * true to indicate that it has successfully saved the settings. + * + * @warning The values in the parameter array cannot be arrays + * + * @param array $params An array of name => value parameters + * + * @return bool + * @since 1.8.0 + */ + public function saveSettings($params) { + if (!$this->canEdit()) { + return false; + } + + // plugin hook handlers should return true to indicate the settings have + // been saved so that default code does not run + $hook_params = array( + 'widget' => $this, + 'params' => $params + ); + if (elgg_trigger_plugin_hook('widget_settings', $this->handler, $hook_params, false) == true) { + return true; + } + + if (is_array($params) && count($params) > 0) { + foreach ($params as $name => $value) { + if (is_array($value)) { + // private settings cannot handle arrays + return false; + } else { + $this->$name = $value; + } + } + $this->save(); } return true; } -}
\ No newline at end of file +} diff --git a/engine/classes/ElggXMLElement.php b/engine/classes/ElggXMLElement.php new file mode 100644 index 000000000..cbd3fc5ce --- /dev/null +++ b/engine/classes/ElggXMLElement.php @@ -0,0 +1,131 @@ +<?php +/** + * A parser for XML that uses SimpleXMLElement + * + * @package Elgg.Core + * @subpackage XML + */ +class ElggXMLElement { + /** + * @var SimpleXMLElement + */ + private $_element; + + /** + * Creates an ElggXMLParser from a string or existing SimpleXMLElement + * + * @param string|SimpleXMLElement $xml The XML to parse + */ + public function __construct($xml) { + if ($xml instanceof SimpleXMLElement) { + $this->_element = $xml; + } else { + // do not load entities + $disable_load_entities = libxml_disable_entity_loader(true); + + $this->_element = new SimpleXMLElement($xml); + + libxml_disable_entity_loader($disable_load_entities); + } + } + + /** + * @return string The name of the element + */ + public function getName() { + return $this->_element->getName(); + } + + /** + * @return string[] The attributes + */ + public function getAttributes() { + //include namespace declarations as attributes + $xmlnsRaw = $this->_element->getNamespaces(); + $xmlns = array(); + foreach ($xmlnsRaw as $key => $val) { + $label = 'xmlns' . ($key ? ":$key" : $key); + $xmlns[$label] = $val; + } + //get attributes and merge with namespaces + $attrRaw = $this->_element->attributes(); + $attr = array(); + foreach ($attrRaw as $key => $val) { + $attr[$key] = $val; + } + $attr = array_merge((array) $xmlns, (array) $attr); + $result = array(); + foreach ($attr as $key => $val) { + $result[$key] = (string) $val; + } + return $result; + } + + /** + * @return string CData + */ + public function getContent() { + return (string) $this->_element; + } + + /** + * @return ElggXMLElement[] Child elements + */ + public function getChildren() { + $children = $this->_element->children(); + $result = array(); + foreach ($children as $val) { + $result[] = new ElggXMLElement($val); + } + + return $result; + } + + /** + * Override -> + * + * @param string $name Property name + * @return mixed + */ + function __get($name) { + switch ($name) { + case 'name': + return $this->getName(); + break; + case 'attributes': + return $this->getAttributes(); + break; + case 'content': + return $this->getContent(); + break; + case 'children': + return $this->getChildren(); + break; + } + return null; + } + + /** + * Override isset + * + * @param string $name Property name + * @return boolean + */ + function __isset($name) { + switch ($name) { + case 'name': + return $this->getName() !== null; + break; + case 'attributes': + return $this->getAttributes() !== null; + break; + case 'content': + return $this->getContent() !== null; + break; + case 'children': + return $this->getChildren() !== null; + break; + } + return false; + } +} diff --git a/engine/classes/ErrorResult.php b/engine/classes/ErrorResult.php index 8dbb7c13f..afad4c740 100644 --- a/engine/classes/ErrorResult.php +++ b/engine/classes/ErrorResult.php @@ -26,7 +26,7 @@ class ErrorResult extends GenericResult { * * @return void */ - public function ErrorResult($message, $code = "", Exception $exception = NULL) { + public function __construct($message, $code = "", Exception $exception = NULL) { if ($code == "") { $code = ErrorResult::$RESULT_FAIL; } @@ -51,4 +51,4 @@ class ErrorResult extends GenericResult { // Return a new error object. return new ErrorResult($message, $code, $exception); } -}
\ No newline at end of file +} diff --git a/engine/classes/ExportException.php b/engine/classes/ExportException.php index 11781d91b..ae8a8e41b 100644 --- a/engine/classes/ExportException.php +++ b/engine/classes/ExportException.php @@ -6,4 +6,4 @@ * @subpackage Exception * */ -class ExportException extends DataFormatException {}
\ No newline at end of file +class ExportException extends DataFormatException {} diff --git a/engine/classes/Exportable.php b/engine/classes/Exportable.php index 1adcd781e..0c1ea5282 100644 --- a/engine/classes/Exportable.php +++ b/engine/classes/Exportable.php @@ -20,4 +20,4 @@ interface Exportable { * @return array */ public function getExportableValues(); -}
\ No newline at end of file +} diff --git a/engine/classes/Friendable.php b/engine/classes/Friendable.php index 588f9ec5a..c308b4598 100644 --- a/engine/classes/Friendable.php +++ b/engine/classes/Friendable.php @@ -101,4 +101,4 @@ interface Friendable { * @return int */ public function countObjects($subtype = ""); -}
\ No newline at end of file +} diff --git a/engine/classes/GenericResult.php b/engine/classes/GenericResult.php index bb8a7e76e..e42e924d1 100644 --- a/engine/classes/GenericResult.php +++ b/engine/classes/GenericResult.php @@ -122,4 +122,4 @@ abstract class GenericResult { return $result; } -}
\ No newline at end of file +} diff --git a/engine/classes/ImportException.php b/engine/classes/ImportException.php index 14287fdf2..909c599d5 100644 --- a/engine/classes/ImportException.php +++ b/engine/classes/ImportException.php @@ -5,4 +5,4 @@ * @package Elgg.Core * @subpackage Exception */ -class ImportException extends DataFormatException {}
\ No newline at end of file +class ImportException extends DataFormatException {} diff --git a/engine/classes/IncompleteEntityException.php b/engine/classes/IncompleteEntityException.php new file mode 100644 index 000000000..8c86edcc6 --- /dev/null +++ b/engine/classes/IncompleteEntityException.php @@ -0,0 +1,10 @@ +<?php +/** + * IncompleteEntityException + * Thrown when constructing an entity that is missing its secondary entity table + * + * @package Elgg.Core + * @subpackage Exception + * @access private + */ +class IncompleteEntityException extends Exception {} diff --git a/engine/classes/Locatable.php b/engine/classes/Locatable.php index 980256cbb..7287d9798 100644 --- a/engine/classes/Locatable.php +++ b/engine/classes/Locatable.php @@ -13,7 +13,7 @@ interface Locatable { * @param string $location Textual representation of location * * @return bool - **/ + */ public function setLocation($location); /** @@ -46,4 +46,4 @@ interface Locatable { * @return string */ public function getLocation(); -}
\ No newline at end of file +} diff --git a/engine/classes/Loggable.php b/engine/classes/Loggable.php index 36db8a692..b9e8bf26b 100644 --- a/engine/classes/Loggable.php +++ b/engine/classes/Loggable.php @@ -59,7 +59,7 @@ interface Loggable { * Return the GUID of the owner of this object. * * @return int - * @deprecated 1.8 Use getOwner() instead + * @deprecated 1.8 Use getOwnerGUID() instead */ public function getObjectOwnerGUID(); -}
\ No newline at end of file +} diff --git a/engine/classes/LoginException.php b/engine/classes/LoginException.php new file mode 100644 index 000000000..7546fa36f --- /dev/null +++ b/engine/classes/LoginException.php @@ -0,0 +1,10 @@ +<?php +/** + * Login Exception Stub + * + * Generic parent class for login exceptions. + * + * @package Elgg.Core + * @subpackage Exceptions.Stub + */ +class LoginException extends Exception {} diff --git a/engine/classes/Notable.php b/engine/classes/Notable.php index e36699dfc..0c21af27d 100644 --- a/engine/classes/Notable.php +++ b/engine/classes/Notable.php @@ -38,4 +38,4 @@ interface Notable { * @return int */ public function getCalendarEndTime(); -}
\ No newline at end of file +} diff --git a/engine/classes/ODDDocument.php b/engine/classes/ODDDocument.php index 4d185aba5..540c35a3b 100644 --- a/engine/classes/ODDDocument.php +++ b/engine/classes/ODDDocument.php @@ -70,8 +70,8 @@ class ODDDocument implements Iterator { public function addElement(ODD $element) { if (!is_array($this->elements)) { $this->elements = array(); - $this->elements[] = $element; } + $this->elements[] = $element; } /** diff --git a/engine/classes/ODDEntity.php b/engine/classes/ODDEntity.php index acdfe2f71..e9bb5da6a 100644 --- a/engine/classes/ODDEntity.php +++ b/engine/classes/ODDEntity.php @@ -32,75 +32,3 @@ class ODDEntity extends ODD { return "entity"; } } - -/** - * ODD Metadata class. - * - * @package Elgg.Core - * @subpackage ODD - */ -class ODDMetaData extends ODD { - - /** - * New ODD metadata - * - * @param unknown_type $uuid Unique ID - * @param unknown_type $entity_uuid Another unique ID - * @param unknown_type $name Name - * @param unknown_type $value Value - * @param unknown_type $type Type - * @param unknown_type $owner_uuid Owner ID - */ - function __construct($uuid, $entity_uuid, $name, $value, $type = "", $owner_uuid = "") { - parent::__construct(); - - $this->setAttribute('uuid', $uuid); - $this->setAttribute('entity_uuid', $entity_uuid); - $this->setAttribute('name', $name); - $this->setAttribute('type', $type); - $this->setAttribute('owner_uuid', $owner_uuid); - $this->setBody($value); - } - - /** - * Returns 'metadata' - * - * @return 'metadata' - */ - protected function getTagName() { - return "metadata"; - } -} - -/** - * ODD Relationship class. - * - * @package Elgg - * @subpackage Core - */ -class ODDRelationship extends ODD { - - /** - * New ODD Relationship - * - * @param unknown_type $uuid1 First UUID - * @param unknown_type $type Type of telationship - * @param unknown_type $uuid2 Second UUId - */ - function __construct($uuid1, $type, $uuid2) { - parent::__construct(); - - $this->setAttribute('uuid1', $uuid1); - $this->setAttribute('type', $type); - $this->setAttribute('uuid2', $uuid2); - } - - /** - * Returns 'relationship' - * - * @return 'relationship' - */ - protected function getTagName() { - return "relationship"; - } -}
\ No newline at end of file diff --git a/engine/classes/ODDMetaData.php b/engine/classes/ODDMetaData.php new file mode 100644 index 000000000..09b653582 --- /dev/null +++ b/engine/classes/ODDMetaData.php @@ -0,0 +1,39 @@ +<?php +/** + * ODD Metadata class. + * + * @package Elgg.Core + * @subpackage ODD + */ +class ODDMetaData extends ODD { + + /** + * New ODD metadata + * + * @param string $uuid Unique ID + * @param string $entity_uuid Another unique ID + * @param string $name Name + * @param string $value Value + * @param string $type Type + * @param string $owner_uuid Owner ID + */ + function __construct($uuid, $entity_uuid, $name, $value, $type = "", $owner_uuid = "") { + parent::__construct(); + + $this->setAttribute('uuid', $uuid); + $this->setAttribute('entity_uuid', $entity_uuid); + $this->setAttribute('name', $name); + $this->setAttribute('type', $type); + $this->setAttribute('owner_uuid', $owner_uuid); + $this->setBody($value); + } + + /** + * Returns 'metadata' + * + * @return string 'metadata' + */ + protected function getTagName() { + return "metadata"; + } +} diff --git a/engine/classes/ODDRelationship.php b/engine/classes/ODDRelationship.php new file mode 100644 index 000000000..8b1fe217b --- /dev/null +++ b/engine/classes/ODDRelationship.php @@ -0,0 +1,33 @@ +<?php +/** + * ODD Relationship class. + * + * @package Elgg + * @subpackage Core + */ +class ODDRelationship extends ODD { + + /** + * New ODD Relationship + * + * @param string $uuid1 First UUID + * @param string $type Type of telationship + * @param string $uuid2 Second UUId + */ + function __construct($uuid1, $type, $uuid2) { + parent::__construct(); + + $this->setAttribute('uuid1', $uuid1); + $this->setAttribute('type', $type); + $this->setAttribute('uuid2', $uuid2); + } + + /** + * Returns 'relationship' + * + * @return string 'relationship' + */ + protected function getTagName() { + return "relationship"; + } +} diff --git a/engine/classes/PluginException.php b/engine/classes/PluginException.php index 56cf29596..a74303695 100644 --- a/engine/classes/PluginException.php +++ b/engine/classes/PluginException.php @@ -8,4 +8,4 @@ * @package Elgg.Core * @subpackage Exception */ -class PluginException extends Exception {}
\ No newline at end of file +class PluginException extends Exception {} diff --git a/engine/classes/RegistrationException.php b/engine/classes/RegistrationException.php index 05af35a01..5246efc25 100644 --- a/engine/classes/RegistrationException.php +++ b/engine/classes/RegistrationException.php @@ -6,4 +6,4 @@ * @package Elgg.Core * @subpackage Exceptions */ -class RegistrationException extends InstallationException {}
\ No newline at end of file +class RegistrationException extends InstallationException {} diff --git a/engine/classes/SuccessResult.php b/engine/classes/SuccessResult.php index f68ddd39e..ab5468ad8 100644 --- a/engine/classes/SuccessResult.php +++ b/engine/classes/SuccessResult.php @@ -15,7 +15,7 @@ class SuccessResult extends GenericResult { * * @param string $result The result */ - public function SuccessResult($result) { + public function __construct($result) { $this->setResult($result); $this->setStatusCode(SuccessResult::$RESULT_SUCCESS); } @@ -31,4 +31,4 @@ class SuccessResult extends GenericResult { // Return a new error object. return new SuccessResult($result); } -}
\ No newline at end of file +} diff --git a/engine/classes/XMLRPCArrayParameter.php b/engine/classes/XMLRPCArrayParameter.php index c0d358d5a..a8edccba7 100644 --- a/engine/classes/XMLRPCArrayParameter.php +++ b/engine/classes/XMLRPCArrayParameter.php @@ -53,4 +53,4 @@ class XMLRPCArrayParameter extends XMLRPCParameter return "<array><data>$params</data></array>"; } -}
\ No newline at end of file +} diff --git a/engine/classes/XMLRPCBase64Parameter.php b/engine/classes/XMLRPCBase64Parameter.php index eb82b24a8..7db0a761c 100644 --- a/engine/classes/XMLRPCBase64Parameter.php +++ b/engine/classes/XMLRPCBase64Parameter.php @@ -25,4 +25,4 @@ class XMLRPCBase64Parameter extends XMLRPCParameter { function __toString() { return "<value><base64>{$value}</base64></value>"; } -}
\ No newline at end of file +} diff --git a/engine/classes/XMLRPCBoolParameter.php b/engine/classes/XMLRPCBoolParameter.php index c53d9ad14..607841cb8 100644 --- a/engine/classes/XMLRPCBoolParameter.php +++ b/engine/classes/XMLRPCBoolParameter.php @@ -27,4 +27,4 @@ class XMLRPCBoolParameter extends XMLRPCParameter { $code = ($this->value) ? "1" : "0"; return "<value><boolean>{$code}</boolean></value>"; } -}
\ No newline at end of file +} diff --git a/engine/classes/XMLRPCCall.php b/engine/classes/XMLRPCCall.php index 3b33f3cee..fd28f1e3e 100644 --- a/engine/classes/XMLRPCCall.php +++ b/engine/classes/XMLRPCCall.php @@ -18,7 +18,7 @@ class XMLRPCCall { * @param string $xml XML */ function __construct($xml) { - $this->_parse($xml); + $this->parse($xml); } /** @@ -45,7 +45,7 @@ class XMLRPCCall { * * @return void */ - private function _parse($xml) { + private function parse($xml) { $xml = xml_to_object($xml); // sanity check @@ -59,4 +59,4 @@ class XMLRPCCall { // parameters $this->params = $xml->children[1]->children; } -}
\ No newline at end of file +} diff --git a/engine/classes/XMLRPCDateParameter.php b/engine/classes/XMLRPCDateParameter.php index 35e02fb37..93bbbd8f5 100644 --- a/engine/classes/XMLRPCDateParameter.php +++ b/engine/classes/XMLRPCDateParameter.php @@ -30,4 +30,4 @@ class XMLRPCDateParameter extends XMLRPCParameter { $value = date('c', $this->value); return "<value><dateTime.iso8601>{$value}</dateTime.iso8601></value>"; } -}
\ No newline at end of file +} diff --git a/engine/classes/XMLRPCErrorResponse.php b/engine/classes/XMLRPCErrorResponse.php index c3dbd0373..425c075cc 100644 --- a/engine/classes/XMLRPCErrorResponse.php +++ b/engine/classes/XMLRPCErrorResponse.php @@ -33,4 +33,4 @@ class XMLRPCErrorResponse extends XMLRPCResponse { public function __toString() { return "<methodResponse><fault><value>{$this->parameters[0]}</value></fault></methodResponse>"; } -}
\ No newline at end of file +} diff --git a/engine/classes/XMLRPCParameter.php b/engine/classes/XMLRPCParameter.php index 5fac33201..ffbad8082 100644 --- a/engine/classes/XMLRPCParameter.php +++ b/engine/classes/XMLRPCParameter.php @@ -13,4 +13,4 @@ abstract class XMLRPCParameter { */ function __construct() { } -}
\ No newline at end of file +} diff --git a/engine/classes/XMLRPCResponse.php b/engine/classes/XMLRPCResponse.php index 2a0319431..a6256d385 100644 --- a/engine/classes/XMLRPCResponse.php +++ b/engine/classes/XMLRPCResponse.php @@ -68,4 +68,4 @@ abstract class XMLRPCResponse { public function addBoolean($value) { $this->addParameter(new XMLRPCBoolParameter($value)); } -}
\ No newline at end of file +} diff --git a/engine/classes/XMLRPCStructParameter.php b/engine/classes/XMLRPCStructParameter.php index 754c92616..694ddf5df 100644 --- a/engine/classes/XMLRPCStructParameter.php +++ b/engine/classes/XMLRPCStructParameter.php @@ -52,4 +52,4 @@ class XMLRPCStructParameter extends XMLRPCParameter { return "<value><struct>$params</struct></value>"; } -}
\ No newline at end of file +} diff --git a/engine/classes/XMLRPCSuccessResponse.php b/engine/classes/XMLRPCSuccessResponse.php index 2f452ef42..e02e82c5c 100644 --- a/engine/classes/XMLRPCSuccessResponse.php +++ b/engine/classes/XMLRPCSuccessResponse.php @@ -19,4 +19,4 @@ class XMLRPCSuccessResponse extends XMLRPCResponse { return "<methodResponse><params>$params</params></methodResponse>"; } -}
\ No newline at end of file +} diff --git a/engine/classes/XmlElement.php b/engine/classes/XmlElement.php index 64d54e6b0..280bba664 100644 --- a/engine/classes/XmlElement.php +++ b/engine/classes/XmlElement.php @@ -17,4 +17,4 @@ class XmlElement { /** Child elements */ public $children; -};
\ No newline at end of file +}; diff --git a/engine/handlers/action_handler.php b/engine/handlers/action_handler.php index a6bdaeae9..bcad110b2 100644 --- a/engine/handlers/action_handler.php +++ b/engine/handlers/action_handler.php @@ -14,7 +14,7 @@ * @link http://docs.elgg.org/Tutorials/Actions */ -require_once("../start.php"); +require_once(dirname(dirname(__FILE__)) . "/start.php"); $action = get_input("action"); -action($action);
\ No newline at end of file +action($action); diff --git a/engine/handlers/cache_handler.php b/engine/handlers/cache_handler.php new file mode 100644 index 000000000..36fc665bb --- /dev/null +++ b/engine/handlers/cache_handler.php @@ -0,0 +1,105 @@ +<?php +/** + * Cache handler. + * + * External access to cached CSS and JavaScript views. The cached file URLS + * should be of the form: cache/<type>/<viewtype>/<name/of/view>.<unique_id>.<type> where + * type is either css or js, view is the name of the cached view, and + * unique_id is an identifier that is updated every time the cache is flushed. + * The simplest way to maintain a unique identifier is to use the lastcache + * variable in Elgg's config object. + * + * @see elgg_register_simplecache_view() + * + * @package Elgg.Core + * @subpackage Cache + */ + +// Get dataroot +require_once(dirname(dirname(__FILE__)) . '/settings.php'); +$mysql_dblink = mysql_connect($CONFIG->dbhost, $CONFIG->dbuser, $CONFIG->dbpass, true); +if (!$mysql_dblink) { + echo 'Cache error: unable to connect to database server'; + exit; +} + +if (!mysql_select_db($CONFIG->dbname, $mysql_dblink)) { + echo 'Cache error: unable to connect to Elgg database'; + exit; +} + +$query = "select name, value from {$CONFIG->dbprefix}datalists + where name in ('dataroot', 'simplecache_enabled')"; + +$result = mysql_query($query, $mysql_dblink); +if (!$result) { + echo 'Cache error: unable to get the data root'; + exit; +} +while ($row = mysql_fetch_object($result)) { + ${$row->name} = $row->value; +} +mysql_free_result($result); + + +$dirty_request = $_GET['request']; +// only alphanumeric characters plus /, ., and _ and no '..' +$filter = array("options" => array("regexp" => "/^(\.?[_a-zA-Z0-9\/]+)+$/")); +$request = filter_var($dirty_request, FILTER_VALIDATE_REGEXP, $filter); +if (!$request || !$simplecache_enabled) { + echo 'Cache error: bad request'; + exit; +} + +// testing showed regex to be marginally faster than array / string functions over 100000 reps +// it won't make a difference in real life and regex is easier to read. +// <type>/<viewtype>/<name/of/view.and.dots>.<ts>.<type> +$regex = '|([^/]+)/([^/]+)/(.+)\.([^\.]+)\.([^.]+)$|'; +preg_match($regex, $request, $matches); + +$type = $matches[1]; +$viewtype = $matches[2]; +$view = $matches[3]; +$ts = $matches[4]; + +// If is the same ETag, content didn't changed. +$etag = $ts; +if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim($_SERVER['HTTP_IF_NONE_MATCH']) == "\"$etag\"") { + header("HTTP/1.1 304 Not Modified"); + exit; +} + +switch ($type) { + case 'css': + header("Content-type: text/css", true); + $view = "css/$view"; + break; + case 'js': + header('Content-type: text/javascript', true); + $view = "js/$view"; + break; +} + +header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', strtotime("+6 months")), true); +header("Pragma: public", true); +header("Cache-Control: public", true); +header("ETag: \"$etag\""); + +$filename = $dataroot . 'views_simplecache/' . md5($viewtype . $view); + +if (file_exists($filename)) { + readfile($filename); +} else { + // someone trying to access a non-cached file or a race condition with cache flushing + mysql_close($mysql_dblink); + require_once(dirname(dirname(__FILE__)) . "/start.php"); + + global $CONFIG; + if (!in_array($view, $CONFIG->views->simplecache)) { + header("HTTP/1.1 404 Not Found"); + exit; + } + + elgg_set_viewtype($viewtype); + echo elgg_view($view); +} diff --git a/engine/handlers/cron_handler.php b/engine/handlers/cron_handler.php deleted file mode 100644 index 537b34f39..000000000 --- a/engine/handlers/cron_handler.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php -/** - * Cron handlers - * - * This file dispatches cron actions. It is called via a URL rewrite in .htaccess - * from http://site/p/. Anything after 'action/' is considered the action - * and will be passed to {@link action()}. - * - * @package Elgg.Core - * @subpackage Actions - * @link http://docs.elgg.org/Tutorials/Actions - * - * @todo - */ - -require_once("../start.php"); -global $CONFIG; - -$period = get_input('period'); -if (!$period) { - throw new CronException(sprintf(elgg_echo('CronException:unknownperiod'), $period)); -} - -// Get a list of parameters -$params = array(); -$params['time'] = time(); - -foreach ($CONFIG->input as $k => $v) { - $params[$k] = $v; -} - -// Data to return to -$std_out = ""; -$old_stdout = ""; -ob_start(); - -$old_stdout = trigger_plugin_hook('cron', $period, $params, $old_stdout); -$std_out = ob_get_clean(); - -// Return event -echo $std_out . $old_stdout;
\ No newline at end of file diff --git a/engine/handlers/export_handler.php b/engine/handlers/export_handler.php new file mode 100644 index 000000000..aa5214c23 --- /dev/null +++ b/engine/handlers/export_handler.php @@ -0,0 +1,118 @@ +<?php +/** + * Export handler. + * + * @package Elgg.Core + * @subpackage Export + */ + +require_once(dirname(dirname(__FILE__)) . "/start.php"); + + +// Get input values, these will be mapped via modrewrite +$guid = get_input("guid"); // guid of the entity + +// For attributes eg http://example.com/odd/73/attr/owner_uuid/ +// or http://example.com/odd/73/metadata/86/ +$type = get_input("type"); // attr, metadata, annotation, relationship +$id_or_name = get_input("idname"); // Either a number or the key name (if attribute) + +$body = ""; +$title = ""; + +// Only export the GUID +if (($guid != "") && ($type == "") && ($id_or_name == "")) { + $entity = get_entity($guid); + + if (!$entity) { + $query = elgg_echo('InvalidParameterException:GUIDNotFound', array($guid)); + throw new InvalidParameterException($query); + } + + $title = "GUID:$guid"; + $body = elgg_view("export/entity", array("entity" => $entity, "uuid" => guid_to_uuid($guid))); + + // Export an individual attribute +} else if (($guid != "") && ($type != "") && ($id_or_name != "")) { + // Get a uuid + $entity = get_entity($guid); + if (!$entity) { + $msg = elgg_echo('InvalidParameterException:GUIDNotFound', array($guid)); + throw new InvalidParameterException($msg); + } + + $uuid = guid_to_uuid($entity->getGUID()) . "$type/$id_or_name/"; + + switch ($type) { + case 'attr' : // @todo: Do this better? - This is a bit of a hack... + $v = $entity->get($id_or_name); + if (!$v) { + $msg = elgg_echo('InvalidParameterException:IdNotExistForGUID', array($id_or_name, $guid)); + throw new InvalidParameterException($msg); + } + + $m = new ElggMetadata(); + + $m->value = $v; + $m->name = $id_or_name; + $m->entity_guid = $guid; + $m->time_created = $entity->time_created; + $m->time_updated = $entity->time_updated; + $m->owner_guid = $entity->owner_guid; + $m->id = $id_or_name; + $m->type = "attr"; + break; + case 'metadata' : + $m = elgg_get_metadata_from_id($id_or_name); + break; + case 'annotation' : + $m = elgg_get_annotation_from_id($id_or_name); + break; + case 'relationship' : + $r = get_relationship($id_or_name); + break; + case 'volatile' : + $m = elgg_trigger_plugin_hook('volatile', 'metadata', array( + 'guid' => $guid, + 'varname' => $id_or_name, + )); + break; + + default : + $msg = elgg_echo('InvalidParameterException:CanNotExportType', array($type)); + throw new InvalidParameterException($msg); + } + + // Render metadata or relationship + if ((!$m) && (!$r)) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:NoDataFound')); + } + + // Exporting metadata? + if ($m) { + if ($m->entity_guid != $entity->guid) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:DoesNotBelong')); + } + + $title = "$type:$id_or_name"; + $body = elgg_view("export/metadata", array("metadata" => $m, "uuid" => $uuid)); + } + + // Exporting relationship + if ($r) { + if (($r->guid_one != $entity->guid) && ($r->guid_two != $entity->guid)) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:DoesNotBelongOrRefer')); + } + + $title = "$type:$id_or_name"; + $body = elgg_view("export/relationship", array("relationship" => $r, "uuid" => $uuid)); + } + + // Something went wrong +} else { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:MissingParameter')); +} + +$content = elgg_view_title($title) . $body; +$body = elgg_view_layout('one_sidebar', array('content' => $content)); +echo elgg_view_page($title, $body); diff --git a/engine/handlers/page_handler.php b/engine/handlers/page_handler.php new file mode 100644 index 000000000..1ed295b7d --- /dev/null +++ b/engine/handlers/page_handler.php @@ -0,0 +1,48 @@ +<?php +/** + * Pages handler. + * + * This file dispatches pages. It is called via a URL rewrite in .htaccess + * from http://site/handler/page1/page2. The first element after site/ is + * the page handler name as registered by {@link elgg_register_page_handler()}. + * The rest of the string is sent to {@link page_handler()}. + * + * Note that the following handler names are reserved by elgg and should not be + * registered by any plugins: + * * action + * * cache + * * services + * * export + * * mt + * * xml-rpc.php + * * rewrite.php + * * tag (deprecated, reserved for backwards compatibility) + * * pg (deprecated, reserved for backwards compatibility) + * + * {@link page_handler()} explodes the pages string by / and sends it to + * the page handler function as registered by {@link elgg_register_page_handler()}. + * If a valid page handler isn't found, plugins have a chance to provide a 404. + * + * @package Elgg.Core + * @subpackage PageHandler + * @link http://docs.elgg.org/Tutorials/PageHandlers + */ + + +// Permanent redirect to pg-less urls +$url = $_SERVER['REQUEST_URI']; +$new_url = preg_replace('#/pg/#', '/', $url, 1); + +if ($url !== $new_url) { + header("HTTP/1.1 301 Moved Permanently"); + header("Location: $new_url"); +} + +require_once(dirname(dirname(__FILE__)) . "/start.php"); + +$handler = get_input('handler'); +$page = get_input('page'); + +if (!page_handler($handler, $page)) { + forward('', '404'); +} diff --git a/engine/handlers/pagehandler.php b/engine/handlers/pagehandler.php deleted file mode 100644 index a92c2f408..000000000 --- a/engine/handlers/pagehandler.php +++ /dev/null @@ -1,27 +0,0 @@ -<?php -/** - * Pages handler. - * - * This file dispatches pages. It is called via a URL rewrite in .htaccess - * from http://site/pg/handler/page1/page2. The first element after 'pg/' is - * the page handler name as registered by {@link register_page_handler()}. - * The rest of the string is sent to {@link page_handler()}. - * - * {@link page_handler()} explodes the pages string by / and sends it to - * the page handler function as registered by {@link register_page_handler()}. - * If a valid page handler isn't found, the user will be forwarded to the site - * front page. - * - * @package Elgg.Core - * @subpackage PageHandler - * @link http://docs.elgg.org/Tutorials/PageHandlers - */ - -require_once("../start.php"); - -$handler = get_input('handler'); -$page = get_input('page'); - -if (!page_handler($handler, $page)) { - forward(); -}
\ No newline at end of file diff --git a/engine/handlers/service_handler.php b/engine/handlers/service_handler.php index c6a7e57c5..9cfcd230f 100644 --- a/engine/handlers/service_handler.php +++ b/engine/handlers/service_handler.php @@ -19,9 +19,9 @@ * @link http://docs.elgg.org/Tutorials/WebServices */ -require_once("../start.php"); +require_once(dirname(dirname(__FILE__)) . "/start.php"); $handler = get_input('handler'); $request = get_input('request'); -service_handler($handler, $request);
\ No newline at end of file +service_handler($handler, $request); diff --git a/engine/handlers/xml-rpc_handler.php b/engine/handlers/xml-rpc_handler.php index 2454c83bc..2ee29e5b7 100644 --- a/engine/handlers/xml-rpc_handler.php +++ b/engine/handlers/xml-rpc_handler.php @@ -10,8 +10,7 @@ * @todo Does this work? */ -require_once("../start.php"); -global $CONFIG; +require_once(dirname(dirname(__FILE__)) . "/start.php"); // Register the error handler error_reporting(E_ALL); @@ -42,4 +41,4 @@ if (!($result instanceof XMLRPCResponse)) { } // Output result -echo elgg_view_page("XML-RPC", elgg_view("xml-rpc/output", array('result' => $result)));
\ No newline at end of file +echo elgg_view_page("XML-RPC", elgg_view("xml-rpc/output", array('result' => $result))); diff --git a/engine/lib/access.php b/engine/lib/access.php index 3eddd97ac..de0693ea8 100644 --- a/engine/lib/access.php +++ b/engine/lib/access.php @@ -1,9 +1,9 @@ <?php /** - * Primary function for Elgg's entity and metadata access systems. + * Functions for Elgg's access system for entities, metadata, and annotations. * * Access is generally saved in the database as access_id. This corresponds to - * one of the ACCESS_* constants defined in {@link elgglib.php}, or the ID of an + * one of the ACCESS_* constants defined in {@link elgglib.php} or the ID of an * access collection. * * @package Elgg.Core @@ -12,30 +12,51 @@ */ /** + * Return an ElggCache static variable cache for the access caches + * + * @staticvar ElggStaticVariableCache $access_cache + * @return \ElggStaticVariableCache + * @access private + */ +function _elgg_get_access_cache() { + /** + * A default filestore cache using the dataroot. + */ + static $access_cache; + + if (!$access_cache) { + $access_cache = new ElggStaticVariableCache('access'); + } + + return $access_cache; +} + +/** * Return a string of access_ids for $user_id appropriate for inserting into an SQL IN clause. * * @uses get_access_array * - * @return string A list of access collections suitable for injection in an SQL call * @link http://docs.elgg.org/Access * @see get_access_array() * * @param int $user_id User ID; defaults to currently logged in user * @param int $site_id Site ID; defaults to current site - * @param bool $flush If set to true, will refresh the access list from the database + * @param bool $flush If set to true, will refresh the access list from the + * database rather than using this function's cache. * - * @return string + * @return string A list of access collections suitable for using in an SQL call + * @access private */ function get_access_list($user_id = 0, $site_id = 0, $flush = false) { - global $CONFIG, $init_finished, $SESSION; - static $access_list; - - if (!isset($access_list) || !$init_finished) { - $access_list = array(); + global $CONFIG, $init_finished; + $cache = _elgg_get_access_cache(); + + if ($flush) { + $cache->clear(); } if ($user_id == 0) { - $user_id = $SESSION['id']; + $user_id = elgg_get_logged_in_user_guid(); } if (($site_id == 0) && (isset($CONFIG->site_id))) { @@ -44,40 +65,55 @@ function get_access_list($user_id = 0, $site_id = 0, $flush = false) { $user_id = (int) $user_id; $site_id = (int) $site_id; - if (isset($access_list[$user_id])) { - return $access_list[$user_id]; - } + $hash = $user_id . $site_id . 'get_access_list'; - $access_list[$user_id] = "(" . implode(",", get_access_array($user_id, $site_id, $flush)) . ")"; + if ($cache[$hash]) { + return $cache[$hash]; + } + + $access_array = get_access_array($user_id, $site_id, $flush); + $access = "(" . implode(",", $access_array) . ")"; - return $access_list[$user_id]; + if ($init_finished) { + $cache[$hash] = $access; + } + + return $access; } /** * Returns an array of access IDs a user is permitted to see. * - * Can be overridden with the access:collections:read, user plugin hook. + * Can be overridden with the 'access:collections:read', 'user' plugin hook. + * + * This returns a list of all the collection ids a user owns or belongs + * to plus public and logged in access levels. If the user is an admin, it includes + * the private access level. * - * @param int $user_id User ID; defaults to currently logged in user - * @param int $site_id Site ID; defaults to current site - * @param boolean $flush If set to true, will refresh the access list from the database + * @internal this is only used in core for creating the SQL where clause when + * retrieving content from the database. The friends access level is handled by + * get_access_sql_suffix(). + * + * @see get_write_access_array() for the access levels that a user can write to. + * + * @param int $user_id User ID; defaults to currently logged in user + * @param int $site_id Site ID; defaults to current site + * @param bool $flush If set to true, will refresh the access ids from the + * database rather than using this function's cache. * * @return array An array of access collections ids - * @see get_access_list() */ function get_access_array($user_id = 0, $site_id = 0, $flush = false) { global $CONFIG, $init_finished; - // @todo everything from the db is cached. - // this cache might be redundant. - static $access_array; + $cache = _elgg_get_access_cache(); - if (!isset($access_array) || (!isset($init_finished)) || (!$init_finished)) { - $access_array = array(); + if ($flush) { + $cache->clear(); } if ($user_id == 0) { - $user_id = get_loggedin_userid(); + $user_id = elgg_get_logged_in_user_guid(); } if (($site_id == 0) && (isset($CONFIG->site_guid))) { @@ -87,35 +123,41 @@ function get_access_array($user_id = 0, $site_id = 0, $flush = false) { $user_id = (int) $user_id; $site_id = (int) $site_id; - if (empty($access_array[$user_id]) || $flush == true) { - $tmp_access_array = array(ACCESS_PUBLIC); + $hash = $user_id . $site_id . 'get_access_array'; + + if ($cache[$hash]) { + $access_array = $cache[$hash]; + } else { + $access_array = array(ACCESS_PUBLIC); // The following can only return sensible data if the user is logged in. - if (isloggedin()) { - $tmp_access_array[] = ACCESS_LOGGED_IN; + if (elgg_is_logged_in()) { + $access_array[] = ACCESS_LOGGED_IN; // Get ACL memberships $query = "SELECT am.access_collection_id" . " FROM {$CONFIG->dbprefix}access_collection_membership am" . " LEFT JOIN {$CONFIG->dbprefix}access_collections ag ON ag.id = am.access_collection_id" - . " WHERE am.user_guid = {$user_id} AND (ag.site_guid = {$site_id} OR ag.site_guid = 0)"; + . " WHERE am.user_guid = $user_id AND (ag.site_guid = $site_id OR ag.site_guid = 0)"; - if ($collections = get_data($query)) { + $collections = get_data($query); + if ($collections) { foreach ($collections as $collection) { if (!empty($collection->access_collection_id)) { - $tmp_access_array[] = $collection->access_collection_id; + $access_array[] = (int)$collection->access_collection_id; } } } // Get ACLs owned. $query = "SELECT ag.id FROM {$CONFIG->dbprefix}access_collections ag "; - $query .= "WHERE ag.owner_guid = {$user_id} AND (ag.site_guid = {$site_id} OR ag.site_guid = 0)"; + $query .= "WHERE ag.owner_guid = $user_id AND (ag.site_guid = $site_id OR ag.site_guid = 0)"; - if ($collections = get_data($query)) { + $collections = get_data($query); + if ($collections) { foreach ($collections as $collection) { if (!empty($collection->id)) { - $tmp_access_array[] = $collection->id; + $access_array[] = (int)$collection->id; } } } @@ -123,21 +165,21 @@ function get_access_array($user_id = 0, $site_id = 0, $flush = false) { $ignore_access = elgg_check_access_overrides($user_id); if ($ignore_access == true) { - $tmp_access_array[] = ACCESS_PRIVATE; + $access_array[] = ACCESS_PRIVATE; } - - $access_array[$user_id] = $tmp_access_array; - } else { - // No user id logged in so we can only access public info - $tmp_return = $tmp_access_array; } - } else { - $tmp_access_array = $access_array[$user_id]; + if ($init_finished) { + $cache[$hash] = $access_array; + } } - $options = array('user_id' => $user_id, 'site_id' => $site_id); - return trigger_plugin_hook('access:collections:read', 'user', $options, $tmp_access_array); + $options = array( + 'user_id' => $user_id, + 'site_id' => $site_id + ); + + return elgg_trigger_plugin_hook('access:collections:read', 'user', $options, $access_array); } /** @@ -157,7 +199,7 @@ function get_default_access(ElggUser $user = null) { return $CONFIG->default_access; } - if (!($user) && (!$user = get_loggedin_user())) { + if (!($user) && (!$user = elgg_get_logged_in_user_entity())) { return $CONFIG->default_access; } @@ -180,11 +222,9 @@ $ENTITY_SHOW_HIDDEN_OVERRIDE = false; /** * Show or hide disabled entities. * - * @access private - * * @param bool $show_hidden Show disabled entities. - * * @return void + * @access private */ function access_show_hidden_entities($show_hidden) { global $ENTITY_SHOW_HIDDEN_OVERRIDE; @@ -194,8 +234,8 @@ function access_show_hidden_entities($show_hidden) { /** * Return current status of showing disabled entities. * - * @access private * @return bool + * @access private */ function access_get_show_hidden_status() { global $ENTITY_SHOW_HIDDEN_OVERRIDE; @@ -203,48 +243,11 @@ function access_get_show_hidden_status() { } /** - * Add annotation restriction - * - * Returns an SQL fragment that is true (or optionally false) if the given user has - * added an annotation with the given name to the given entity. - * - * @todo This is fairly generic so perhaps it could be moved to annotations.php - * - * @param string $annotation_name Name of the annotation - * @param string $entity_guid SQL GUID of entity the annotation is attached to. - * @param string $owner_guid SQL string that evaluates to the GUID of the annotation owner - * @param boolean $exists If true, returns BOOL if the annotation exists - * - * @return string An SQL fragment suitable for inserting into a WHERE clause - * @todo Document and maybe even remove. At least rename to something that makes sense. - */ -function get_annotation_sql($annotation_name, $entity_guid, $owner_guid, $exists) { - global $CONFIG; - - if ($exists) { - $not = ''; - } else { - $not = 'NOT'; - } - - $sql = <<<END -$not EXISTS (SELECT * FROM {$CONFIG->dbprefix}annotations a -INNER JOIN {$CONFIG->dbprefix}metastrings ms ON (a.name_id = ms.id) -WHERE ms.string = '$annotation_name' -AND a.entity_guid = $entity_guid -AND a.owner_guid = $owner_guid) -END; - return $sql; -} - -/** * Returns the SQL where clause for a table with a access_id and enabled columns. * - * This handles returning where clauses for ACCESS_FRIENDS, and the currently - * unused block and filter lists. - * - * @warning If an admin is logged in or {@link elgg_set_ignore_access()} is true, - * this will return blank. + * This handles returning where clauses for ACCESS_FRIENDS and the currently + * unused block and filter lists in addition to using get_access_list() for + * access collections and the standard access levels. * * @param string $table_prefix Optional table. prefix for the access code. * @param int $owner The guid to check access for. Defaults to logged in user. @@ -260,11 +263,11 @@ function get_access_sql_suffix($table_prefix = '', $owner = null) { $enemies_bit = ""; if ($table_prefix) { - $table_prefix = sanitise_string($table_prefix) . "."; + $table_prefix = sanitise_string($table_prefix) . "."; } if (!isset($owner)) { - $owner = get_loggedin_userid(); + $owner = elgg_get_logged_in_user_guid(); } if (!$owner) { @@ -277,6 +280,7 @@ function get_access_sql_suffix($table_prefix = '', $owner = null) { if ($ignore_access) { $sql = " (1 = 1) "; } else if ($owner != -1) { + // we have an entity's guid and auto check for friend relationships $friends_bit = "{$table_prefix}access_id = " . ACCESS_FRIENDS . " AND {$table_prefix}owner_guid IN ( SELECT guid_one FROM {$CONFIG->dbprefix}entity_relationships @@ -285,14 +289,15 @@ function get_access_sql_suffix($table_prefix = '', $owner = null) { $friends_bit = '(' . $friends_bit . ') OR '; + // @todo untested and unsupported at present if ((isset($CONFIG->user_block_and_filter_enabled)) && ($CONFIG->user_block_and_filter_enabled)) { // check to see if the user is in the entity owner's block list // or if the entity owner is in the user's filter list // if so, disallow access - $enemies_bit = get_annotation_sql('elgg_block_list', "{$table_prefix}owner_guid", $owner, false); + $enemies_bit = get_access_restriction_sql('elgg_block_list', "{$table_prefix}owner_guid", $owner, false); $enemies_bit = '(' . $enemies_bit - . ' AND ' . get_annotation_sql('elgg_filter_list', $owner, "{$table_prefix}owner_guid", false) + . ' AND ' . get_access_restriction_sql('elgg_filter_list', $owner, "{$table_prefix}owner_guid", false) . ')'; } } @@ -319,19 +324,59 @@ function get_access_sql_suffix($table_prefix = '', $owner = null) { } /** - * Can $user access $entity. + * Get the where clause for an access restriction based on annotations + * + * Returns an SQL fragment that is true (or optionally false) if the given user has + * added an annotation with the given name to the given entity. + * + * @warning this is a private function for an untested capability and will likely + * be removed from a future version of Elgg. + * + * @param string $annotation_name Name of the annotation + * @param string $entity_guid SQL GUID of entity the annotation is attached to. + * @param string $owner_guid SQL string that evaluates to the GUID of the annotation owner + * @param boolean $exists If true, returns BOOL if the annotation exists + * + * @return string An SQL fragment suitable for inserting into a WHERE clause + * @access private + */ +function get_access_restriction_sql($annotation_name, $entity_guid, $owner_guid, $exists) { + global $CONFIG; + + if ($exists) { + $not = ''; + } else { + $not = 'NOT'; + } + + $sql = <<<END +$not EXISTS (SELECT * FROM {$CONFIG->dbprefix}annotations a +INNER JOIN {$CONFIG->dbprefix}metastrings ms ON (a.name_id = ms.id) +WHERE ms.string = '$annotation_name' +AND a.entity_guid = $entity_guid +AND a.owner_guid = $owner_guid) +END; + return $sql; +} + +/** + * Can a user access an entity. * * @warning If a logged in user doesn't have access to an entity, the * core engine will not load that entity. * - * @tip This is mostly useful for checking if a 3rd user has access - * to an entity that is currently loaded. + * @tip This is mostly useful for checking if a user other than the logged in + * user has access to an entity that is currently loaded. + * + * @todo This function would be much more useful if we could pass the guid of the + * entity to test access for. We need to be able to tell whether the entity exists + * and whether the user has access to the entity. * * @param ElggEntity $entity The entity to check access for. * @param ElggUser $user Optionally user to check access for. Defaults to - * logged in user (which doesn't make sense). + * logged in user (which is a useless default). * - * @return boolean True if the user can access the entity + * @return bool * @link http://docs.elgg.org/Access */ function has_access_to_entity($entity, $user = null) { @@ -354,23 +399,41 @@ function has_access_to_entity($entity, $user = null) { } /** - * Returns an array of access permissions that the user is allowed to save objects with. - * Permissions are of the form ('id' => 'Description') + * Returns an array of access permissions that the user is allowed to save content with. + * Permissions returned are of the form (id => 'name'). + * + * Example return value in English: + * array( + * 0 => 'Private', + * -2 => 'Friends', + * 1 => 'Logged in users', + * 2 => 'Public', + * 34 => 'My favorite friends', + * ); + * + * Plugin hook of 'access:collections:write', 'user' + * + * @warning this only returns access collections that the user owns plus the + * standard access levels. It does not return access collections that the user + * belongs to such as the access collection for a group. * * @param int $user_id The user's GUID. * @param int $site_id The current site. - * @param bool $flush If this is set to true, this will ignore any cached version + * @param bool $flush If this is set to true, this will ignore a cached access array * * @return array List of access permissions * @link http://docs.elgg.org/Access */ function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) { - global $CONFIG; - //@todo this is probably not needed since caching happens at the DB level. - static $access_array; + global $CONFIG, $init_finished; + $cache = _elgg_get_access_cache(); + + if ($flush) { + $cache->clear(); + } if ($user_id == 0) { - $user_id = get_loggedin_userid(); + $user_id = elgg_get_logged_in_user_guid(); } if (($site_id == 0) && (isset($CONFIG->site_id))) { @@ -380,34 +443,78 @@ function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) { $user_id = (int) $user_id; $site_id = (int) $site_id; - if (empty($access_array[$user_id]) || $flush == true) { + $hash = $user_id . $site_id . 'get_write_access_array'; + + if ($cache[$hash]) { + $access_array = $cache[$hash]; + } else { + // @todo is there such a thing as public write access? + $access_array = array( + ACCESS_PRIVATE => elgg_echo("PRIVATE"), + ACCESS_FRIENDS => elgg_echo("access:friends:label"), + ACCESS_LOGGED_IN => elgg_echo("LOGGED_IN"), + ACCESS_PUBLIC => elgg_echo("PUBLIC") + ); + $query = "SELECT ag.* FROM {$CONFIG->dbprefix}access_collections ag "; - $query .= " WHERE (ag.site_guid = {$site_id} OR ag.site_guid = 0)"; - $query .= " AND (ag.owner_guid = {$user_id})"; - $query .= " AND ag.id >= 3"; - - $tmp_access_array = array( - ACCESS_PRIVATE => elgg_echo("PRIVATE"), - ACCESS_FRIENDS => elgg_echo("access:friends:label"), - ACCESS_LOGGED_IN => elgg_echo("LOGGED_IN"), - ACCESS_PUBLIC => elgg_echo("PUBLIC") - ); - if ($collections = get_data($query)) { + $query .= " WHERE (ag.site_guid = $site_id OR ag.site_guid = 0)"; + $query .= " AND (ag.owner_guid = $user_id)"; + + $collections = get_data($query); + if ($collections) { foreach ($collections as $collection) { - $tmp_access_array[$collection->id] = $collection->name; + $access_array[$collection->id] = $collection->name; } } - $access_array[$user_id] = $tmp_access_array; + if ($init_finished) { + $cache[$hash] = $access_array; + } + } + + $options = array( + 'user_id' => $user_id, + 'site_id' => $site_id + ); + return elgg_trigger_plugin_hook('access:collections:write', 'user', + $options, $access_array); +} + +/** + * Can the user change this access collection? + * + * Use the plugin hook of 'access:collections:write', 'user' to change this. + * @see get_write_access_array() for details on the hook. + * + * Respects access control disabling for admin users and {@see elgg_set_ignore_access()} + * + * @see get_write_access_array() + * + * @param int $collection_id The collection id + * @param mixed $user_guid The user GUID to check for. Defaults to logged in user. + * @return bool + */ +function can_edit_access_collection($collection_id, $user_guid = null) { + if ($user_guid) { + $user = get_entity((int) $user_guid); } else { - $tmp_access_array = $access_array[$user_id]; + $user = elgg_get_logged_in_user_entity(); + } + + $collection = get_access_collection($collection_id); + + if (!($user instanceof ElggUser) || !$collection) { + return false; } - $options = array('user_id' => $user_id, 'site_id' => $site_id); - $tmp_access_array = trigger_plugin_hook('access:collections:write', 'user', - $options, $tmp_access_array); + $write_access = get_write_access_array($user->getGUID(), 0, true); - return $tmp_access_array; + // don't ignore access when checking users. + if ($user_guid) { + return array_key_exists($collection_id, $write_access); + } else { + return elgg_get_ignore_access() || array_key_exists($collection_id, $write_access); + } } /** @@ -416,6 +523,8 @@ function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) { * Access colletions allow plugins and users to create granular access * for entities. * + * Triggers plugin hook 'access:collections:addcollection', 'collection' + * * @internal Access collections are stored in the access_collections table. * Memberships to collections are in access_collections_membership. * @@ -423,7 +532,7 @@ function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) { * @param int $owner_guid The GUID of the owner (default: currently logged in user). * @param int $site_guid The GUID of the site (default: current site). * - * @return int|false Depending on success (the collection ID if successful). + * @return int|false The collection ID if successful and false on failure. * @link http://docs.elgg.org/Access/Collections * @see update_access_collection() * @see delete_access_collection() @@ -437,7 +546,7 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { } if ($owner_guid == 0) { - $owner_guid = get_loggedin_userid(); + $owner_guid = elgg_get_logged_in_user_guid(); } if (($site_guid == 0) && (isset($CONFIG->site_guid))) { $site_guid = $CONFIG->site_guid; @@ -448,7 +557,8 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { SET name = '{$name}', owner_guid = {$owner_guid}, site_guid = {$site_guid}"; - if (!$id = insert_data($q)) { + $id = insert_data($q); + if (!$id) { return false; } @@ -456,7 +566,7 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { 'collection_id' => $id ); - if (!trigger_plugin_hook('access:collections:addcollection', 'collection', $params, true)) { + if (!elgg_trigger_plugin_hook('access:collections:addcollection', 'collection', $params, true)) { return false; } @@ -467,7 +577,7 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { * Updates the membership in an access collection. * * @warning Expects a full list of all members that should - * be part o the access collection + * be part of the access collection * * @note This will run all hooks associated with adding or removing * members to access collections. @@ -475,45 +585,36 @@ function create_access_collection($name, $owner_guid = 0, $site_guid = 0) { * @param int $collection_id The ID of the collection. * @param array $members Array of member GUIDs * - * @return true|false Depending on success + * @return bool * @link http://docs.elgg.org/Access/Collections * @see add_user_to_access_collection() * @see remove_user_from_access_collection() */ function update_access_collection($collection_id, $members) { - global $CONFIG; + $acl = get_access_collection($collection_id); - $collection_id = (int) $collection_id; + if (!$acl) { + return false; + } $members = (is_array($members)) ? $members : array(); - $collections = get_write_access_array(); - - if (array_key_exists($collection_id, $collections)) { - $cur_members = get_members_of_access_collection($collection_id, true); - $cur_members = (is_array($cur_members)) ? $cur_members : array(); + $cur_members = get_members_of_access_collection($collection_id, true); + $cur_members = (is_array($cur_members)) ? $cur_members : array(); - $remove_members = array_diff($cur_members, $members); - $add_members = array_diff($members, $cur_members); - - $params = array( - 'collection_id' => $collection_id, - 'members' => $members, - 'add_members' => $add_members, - 'remove_members' => $remove_members - ); + $remove_members = array_diff($cur_members, $members); + $add_members = array_diff($members, $cur_members); - foreach ($add_members as $guid) { - add_user_to_access_collection($guid, $collection_id); - } + $result = true; - foreach ($remove_members as $guid) { - remove_user_from_access_collection($guid, $collection_id); - } + foreach ($add_members as $guid) { + $result = $result && add_user_to_access_collection($guid, $collection_id); + } - return true; + foreach ($remove_members as $guid) { + $result = $result && remove_user_from_access_collection($guid, $collection_id); } - return false; + return $result; } /** @@ -527,27 +628,25 @@ function update_access_collection($collection_id, $members) { * @see update_access_collection() */ function delete_access_collection($collection_id) { + global $CONFIG; + $collection_id = (int) $collection_id; - $collections = get_write_access_array(null, null, TRUE); $params = array('collection_id' => $collection_id); - if (!trigger_plugin_hook('access:collections:deletecollection', 'collection', $params, true)) { + if (!elgg_trigger_plugin_hook('access:collections:deletecollection', 'collection', $params, true)) { return false; } - if (array_key_exists($collection_id, $collections)) { - global $CONFIG; - $query = "delete from {$CONFIG->dbprefix}access_collection_membership" - . " where access_collection_id = {$collection_id}"; - delete_data($query); + // Deleting membership doesn't affect result of deleting ACL. + $q = "DELETE FROM {$CONFIG->dbprefix}access_collection_membership + WHERE access_collection_id = {$collection_id}"; + delete_data($q); - $query = "delete from {$CONFIG->dbprefix}access_collections where id = {$collection_id}"; - delete_data($query); - return true; - } else { - return false; - } + $q = "DELETE FROM {$CONFIG->dbprefix}access_collections + WHERE id = {$collection_id}"; + $result = delete_data($q); + return (bool)$result; } /** @@ -556,9 +655,11 @@ function delete_access_collection($collection_id) { * @note This doesn't return the members of an access collection, * just the database row of the actual collection. * + * @see get_members_of_access_collection() + * * @param int $collection_id The collection ID * - * @return array|false + * @return object|false */ function get_access_collection($collection_id) { global $CONFIG; @@ -573,91 +674,88 @@ function get_access_collection($collection_id) { /** * Adds a user to an access collection. * - * Emits the access:collections:add_user, collection plugin hook. + * Triggers the 'access:collections:add_user', 'collection' plugin hook. * * @param int $user_guid The GUID of the user to add * @param int $collection_id The ID of the collection to add them to * - * @return true|false Depending on success - * @link http://docs.elgg.org/Access/Collections + * @return bool * @see update_access_collection() * @see remove_user_from_access_collection() + * @link http://docs.elgg.org/Access/Collections */ function add_user_to_access_collection($user_guid, $collection_id) { + global $CONFIG; + $collection_id = (int) $collection_id; $user_guid = (int) $user_guid; - $collections = get_write_access_array(); + $user = get_user($user_guid); - if (!($collection = get_access_collection($collection_id))) { + $collection = get_access_collection($collection_id); + + if (!($user instanceof Elgguser) || !$collection) { return false; } - if ((array_key_exists($collection_id, $collections) || $collection->owner_guid == 0) - && $user = get_user($user_guid)) { - global $CONFIG; - - $params = array( - 'collection_id' => $collection_id, - 'user_guid' => $user_guid - ); - - if (!trigger_plugin_hook('access:collections:add_user', 'collection', $params, true)) { - return false; - } - - try { - $query = "insert into {$CONFIG->dbprefix}access_collection_membership" - . " set access_collection_id = {$collection_id}, user_guid = {$user_guid}"; - insert_data($queyr); - } catch (DatabaseException $e) { - // nothing. - } - return true; + $params = array( + 'collection_id' => $collection_id, + 'user_guid' => $user_guid + ); + $result = elgg_trigger_plugin_hook('access:collections:add_user', 'collection', $params, true); + if ($result == false) { + return false; } - return false; + // if someone tries to insert the same data twice, we do a no-op on duplicate key + $q = "INSERT INTO {$CONFIG->dbprefix}access_collection_membership + SET access_collection_id = $collection_id, user_guid = $user_guid + ON DUPLICATE KEY UPDATE user_guid = user_guid"; + $result = insert_data($q); + + return $result !== false; } /** * Removes a user from an access collection. * - * Emits the access:collections:remove_user, collection plugin hook. + * Triggers the 'access:collections:remove_user', 'collection' plugin hook. * * @param int $user_guid The user GUID * @param int $collection_id The access collection ID * - * @return true|false Depending on success + * @return bool + * @see update_access_collection() + * @see remove_user_from_access_collection() + * @link http://docs.elgg.org/Access/Collections */ function remove_user_from_access_collection($user_guid, $collection_id) { + global $CONFIG; + $collection_id = (int) $collection_id; $user_guid = (int) $user_guid; - $collections = get_write_access_array(); - $user = $user = get_user($user_guid); + $user = get_user($user_guid); - if (!($collection = get_access_collection($collection_id))) { + $collection = get_access_collection($collection_id); + + if (!($user instanceof Elgguser) || !$collection) { return false; } - if ((array_key_exists($collection_id, $collections) || $collection->owner_guid == 0) && $user) { - global $CONFIG; - $params = array( - 'collection_id' => $collection_id, - 'user_guid' => $user_guid - ); - - if (!trigger_plugin_hook('access:collections:remove_user', 'collection', $params, true)) { - return false; - } - - delete_data("delete from {$CONFIG->dbprefix}access_collection_membership " - . "where access_collection_id = {$collection_id} and user_guid = {$user_guid}"); - - return true; + $params = array( + 'collection_id' => $collection_id, + 'user_guid' => $user_guid + ); + if (!elgg_trigger_plugin_hook('access:collections:remove_user', 'collection', $params, true)) { + return false; } - return false; + $q = "DELETE FROM {$CONFIG->dbprefix}access_collection_membership + WHERE access_collection_id = {$collection_id} + AND user_guid = {$user_guid}"; + + return (bool)delete_data($q); } /** @@ -725,97 +823,13 @@ function get_members_of_access_collection($collection, $idonly = FALSE) { } /** - * Displays a user's access collections, using the friends/collections view - * - * @param int $owner_guid The GUID of the owning user - * - * @return string A formatted rendition of the collections - * @todo Move to the friends/collection.php page. - */ -function elgg_view_access_collections($owner_guid) { - if ($collections = get_user_access_collections($owner_guid)) { - foreach ($collections as $key => $collection) { - $collections[$key]->members = get_members_of_access_collection($collection->id, true); - $collections[$key]->entities = get_user_friends($owner_guid, "", 9999); - } - } - - return elgg_view('friends/collections', array('collections' => $collections)); -} - -/** - * Get entities with the specified access collection id. - * - * @deprecated 1.7. Use elgg_get_entities_from_access_id() - * - * @param int $collection_id ID of collection - * @param string $entity_type Type of entities - * @param string $entity_subtype Subtype of entities - * @param int $owner_guid Guid of owner - * @param int $limit Limit of number of entities to return - * @param int $offset Skip this many entities - * @param string $order_by Column to order by - * @param int $site_guid The site guid - * @param bool $count Return a count or entities - * - * @return array - */ -function get_entities_from_access_id($collection_id, $entity_type = "", $entity_subtype = "", - $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { - // log deprecated warning - elgg_deprecated_notice('get_entities_from_access_id() was deprecated by elgg_get_entities()', 1.7); - - if (!$collection_id) { - return FALSE; - } - - // build the options using given parameters - $options = array(); - $options['limit'] = $limit; - $options['offset'] = $offset; - $options['count'] = $count; - - if ($entity_type) { - $options['type'] = sanitise_string($entity_type); - } - - if ($entity_subtype) { - $options['subtype'] = $entity_subtype; - } - - if ($site_guid) { - $options['site_guid'] = $site_guid; - } - - if ($order_by) { - $options['order_by'] = sanitise_string("e.time_created, $order_by"); - } - - if ($owner_guid) { - if (is_array($owner_guid)) { - $options['owner_guids'] = $owner_guid; - } else { - $options['owner_guid'] = $owner_guid; - } - } - - if ($site_guid) { - $options['site_guid'] = $site_guid; - } - - $options['access_id'] = $collection_id; - - return elgg_get_entities_from_access_id($options); -} - -/** * Return entities based upon access id. * - * @param array $options Any options accepted by {@link elgg_get_entities()} and: + * @param array $options Any options accepted by {@link elgg_get_entities()} and * access_id => int The access ID of the entity. * * @see elgg_get_entities() - * @return array + * @return mixed If count, int. If not count, array. false on errors. * @since 1.7.0 */ function elgg_get_entities_from_access_id(array $options = array()) { @@ -848,61 +862,37 @@ function elgg_get_entities_from_access_id(array $options = array()) { * @see elgg_list_entities() * @see elgg_get_entities_from_access_id() * - * @return str + * @return string */ function elgg_list_entities_from_access_id(array $options = array()) { return elgg_list_entities($options, 'elgg_get_entities_from_access_id'); } /** - * @return str - * @deprecated 1.8 Use elgg_list_entities_from_access_id() - */ -function list_entities_from_access_id($access_id, $entity_type = "", $entity_subtype = "", - $owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = true, $pagination = true) { - - elgg_deprecated_notice("All list_entities* functions were deprecated in 1.8. Use elgg_list_entities* instead.", 1.8); - - echo elgg_list_entities_from_access_id(array( - 'access_id' => $access_id, - 'types' => $entity_type, - 'subtypes' => $entity_subtype, - 'owner_guids' => $owner_guid, - 'limit' => $limit, - 'full_view' => $fullview, - 'view_type_toggle' => $viewtypetoggle, - 'pagination' => $pagination, - )); -} - -/** * Return the name of an ACCESS_* constant or a access collection, * but only if the user has write access on that ACL. * * @warning This function probably doesn't work how it's meant to. * - * @param int $entity_accessid The entity's access id + * @param int $entity_access_id The entity's access id * - * @return string e.g. Public, Private etc + * @return string 'Public', 'Private', etc. * @since 1.7.0 * @todo I think this probably wants get_access_array() instead of get_write_access_array(), * but those two functions return different types of arrays. */ -function get_readable_access_level($entity_accessid) { - $access = (int) $entity_accessid; +function get_readable_access_level($entity_access_id) { + $access = (int) $entity_access_id; //get the access level for object in readable string $options = get_write_access_array(); - //@todo Really? Use array_key_exists() - foreach ($options as $key => $option) { - if ($key == $access) { - $entity_acl = htmlentities($option, ENT_QUOTES, 'UTF-8'); - return $entity_acl; - break; - } + if (array_key_exists($access, $options)) { + return $options[$access]; } - return false; + + // return 'Limited' if the user does not have access to the access collection + return elgg_echo('access:limited:label'); } /** @@ -911,13 +901,15 @@ function get_readable_access_level($entity_accessid) { * The access system will not return entities in any getter * functions if the user doesn't have access. * - * @internal For performance reasons this is done at the database level. + * @internal For performance reasons this is done at the database access clause level. * * @tip Use this to access entities in automated scripts * when no user is logged in. * - * @warning This will not show disabled entities. Use {@link $ENTITY_SHOW_HIDDEN_OVERRIDE} - * for that. + * @note This clears the access cache. + * + * @warning This will not show disabled entities. + * Use {@link access_show_hidden_entities()} to access disabled entities. * * @param bool $ignore If true, disables all access checks. * @@ -927,6 +919,8 @@ function get_readable_access_level($entity_accessid) { * @see elgg_get_ignore_access() */ function elgg_set_ignore_access($ignore = true) { + $cache = _elgg_get_access_cache(); + $cache->clear(); $elgg_access = elgg_get_access_object(); return $elgg_access->setIgnoreAccess($ignore); } @@ -944,17 +938,20 @@ function elgg_get_ignore_access() { } /** - * Decides if the access system is being ignored. + * Decides if the access system should be ignored for a user. + * + * Returns true (meaning ignore access) if either of these 2 conditions are true: + * 1) an admin user guid is passed to this function. + * 2) {@link elgg_get_ignore_access()} returns true. * - * The access system can be ignored if 1) an admin user is logged in - * or 2) {@link elgg_set_ignore_access()} was called with true. + * @see elgg_set_ignore_access() * - * @param mixed $user_guid The user to check against. Defaults to logged in. + * @param int $user_guid The user to check against. * * @return bool * @since 1.7.0 */ -function elgg_check_access_overrides($user_guid = null) { +function elgg_check_access_overrides($user_guid = 0) { if (!$user_guid || $user_guid <= 0) { $is_admin = false; } else { @@ -967,6 +964,7 @@ function elgg_check_access_overrides($user_guid = null) { /** * Returns the ElggAccess object. * + * // @todo comment is incomplete * This is used to * * @return ElggAccess @@ -988,7 +986,8 @@ function elgg_get_access_object() { * * @global bool $init_finished * @access private - * @todo investigate why this is needed + * @todo This is required to tell the access system to start caching because + * calls are made while in ignore access mode and before the user is logged in. */ $init_finished = false; @@ -1006,17 +1005,35 @@ function access_init() { } /** - * Check if the access system should be overridden. + * Overrides the access system if appropriate. * * Allows admin users and calls after {@link elgg_set_ignore_access} to - * by pass the access system. + * bypass the access system. + * + * Registered for the 'permissions_check', 'all' and the + * 'container_permissions_check', 'all' plugin hooks. * + * Returns true to override the access system or null if no change is needed. + * + * @param string $hook + * @param string $type + * @param bool $value + * @param array $params * @return true|null - * @since 1.7.0 - * @elgg_event_handler permissions_check all + * @access private */ -function elgg_override_permissions_hook() { - $user_guid = get_loggedin_userid(); +function elgg_override_permissions($hook, $type, $value, $params) { + $user = elgg_extract('user', $params); + if ($user) { + $user_guid = $user->getGUID(); + } else { + $user_guid = elgg_get_logged_in_user_guid(); + } + + // don't do this so ignore access still works with no one logged in + //if (!$user instanceof ElggUser) { + // return false; + //} // check for admin if ($user_guid && elgg_is_admin_user($user_guid)) { @@ -1032,9 +1049,30 @@ function elgg_override_permissions_hook() { return NULL; } -// This function will let us know when 'init' has finished -register_elgg_event_handler('init', 'system', 'access_init', 9999); +/** + * Runs unit tests for the entities object. + * + * @param string $hook + * @param string $type + * @param array $value + * @param array $params + * @return array + * + * @access private + */ +function access_test($hook, $type, $value, $params) { + global $CONFIG; + + $value[] = $CONFIG->path . 'engine/tests/api/access_collections.php'; + return $value; +} + +// Tell the access functions the system has booted, plugins are loaded, +// and the user is logged in so it can start caching +elgg_register_event_handler('ready', 'system', 'access_init'); // For overrided permissions -register_plugin_hook('permissions_check', 'all', 'elgg_override_permissions_hook'); -register_plugin_hook('container_permissions_check', 'all', 'elgg_override_permissions_hook');
\ No newline at end of file +elgg_register_plugin_hook_handler('permissions_check', 'all', 'elgg_override_permissions'); +elgg_register_plugin_hook_handler('container_permissions_check', 'all', 'elgg_override_permissions'); + +elgg_register_plugin_hook_handler('unit_test', 'system', 'access_test');
\ No newline at end of file diff --git a/engine/lib/actions.php b/engine/lib/actions.php index bcd7e759e..8047914ac 100644 --- a/engine/lib/actions.php +++ b/engine/lib/actions.php @@ -2,21 +2,23 @@ /** * Elgg Actions * - * Actions are the primary controllers (The C in MVC) in Elgg. The are - * registered by {@link register_elgg_action()} and are called either by URL - * http://elggsite.org/action/action_name or {@link action($action_name}. For - * URLs, rewrite a rule in .htaccess passes the action name to - * engine/handlers/action_handler.php, which dispatches the action. + * Actions are one of the primary controllers (The C in MVC) in Elgg. They are + * registered by {@link register_elgg_action()} and are called by URL + * http://elggsite.org/action/action_name. For URLs, a rewrite rule in + * .htaccess passes the action name to engine/handlers/action_handler.php, + * which dispatches the request for the action. * - * An action name should be registered to exactly one file in the system, usually under - * the actions/ directory. + * An action name must be registered to a file in the system. Core actions are + * found in /actions/ and plugin actions are usually under /mod/<plugin>/actions/. + * It is recommended that actions be namespaced to avoid collisions. * * All actions require security tokens. Using the {@elgg_view input/form} view - * will automatically add tokens as hidden inputs. To manually add hidden inputs, - * use the {@elgg_view input/securitytoken} view. + * will automatically add tokens as hidden inputs as will the elgg_view_form() + * function. To manually add hidden inputs, use the {@elgg_view input/securitytoken} view. * * To include security tokens for actions called via GET, use - * {@link elgg_add_security_tokens_to_url()}. + * {@link elgg_add_security_tokens_to_url()} or specify is_action as true when + * using {@lgg_view output/url}. * * Action tokens can be manually generated by using {@link generate_action_token()}. * @@ -31,30 +33,30 @@ */ /** -* Perform an action. -* -* This function executes the action with name $action as -* registered by {@link register_action()}. -* -* The plugin hook action, $action_name will be emitted before -* the action is executed. If a handler returns false, it will -* prevent the action from being called. -* -* @note If an action isn't registered in the system or is registered -* to an unavailable file the user will be forwarded to the site front -* page and an error will be emitted via {@link regiser_error()}. -* -* @warning All actions require {@link http://docs.elgg.org/Actions/Tokens Action Tokens}. -* @warning Most plugin shouldn't call this manually. -* -* @param string $action The requested action -* @param string $forwarder Optionally, the location to forward to -* -* @link http://docs.elgg.org/Actions -* @see register_action() -* -* @return void -*/ + * Perform an action. + * + * This function executes the action with name $action as registered + * by {@link elgg_register_action()}. + * + * The plugin hook 'action', $action_name will be triggered before the action + * is executed. If a handler returns false, it will prevent the action script + * from being called. + * + * @note If an action isn't registered in the system or is registered + * to an unavailable file the user will be forwarded to the site front + * page and an error will be emitted via {@link register_error()}. + * + * @warning All actions require {@link http://docs.elgg.org/Actions/Tokens Action Tokens}. + * + * @param string $action The requested action + * @param string $forwarder Optionally, the location to forward to + * + * @link http://docs.elgg.org/Actions + * @see elgg_register_action() + * + * @return void + * @access private + */ function action($action, $forwarder = "") { global $CONFIG; @@ -63,99 +65,83 @@ function action($action, $forwarder = "") { // @todo REMOVE THESE ONCE #1509 IS IN PLACE. // Allow users to disable plugins without a token in order to // remove plugins that are incompatible. - // Login and logout are for convenience. + // Logout for convenience. // file/download (see #2010) $exceptions = array( 'admin/plugins/disable', 'logout', - 'login', 'file/download', ); if (!in_array($action, $exceptions)) { - // All actions require a token. - action_gatekeeper(); + action_gatekeeper($action); } $forwarder = str_replace(elgg_get_site_url(), "", $forwarder); $forwarder = str_replace("http://", "", $forwarder); $forwarder = str_replace("@", "", $forwarder); - if (substr($forwarder, 0, 1) == "/") { $forwarder = substr($forwarder, 1); } - if (isset($CONFIG->actions[$action])) { - if ((isadminloggedin()) || (!$CONFIG->actions[$action]['admin'])) { - if ($CONFIG->actions[$action]['public'] || get_loggedin_userid()) { - - // Trigger action event - // @todo This is only called before the primary action is called. - $event_result = true; - $event_result = trigger_plugin_hook('action', $action, null, $event_result); - - // Include action - // Event_result being false doesn't produce an error - // since i assume this will be handled in the hook itself. - // @todo make this better! - if ($event_result) { - if (!include($CONFIG->actions[$action]['file'])) { - register_error(sprintf(elgg_echo('actionnotfound'), $action)); - } - } - } else { - register_error(elgg_echo('actionloggedout')); + if (!isset($CONFIG->actions[$action])) { + register_error(elgg_echo('actionundefined', array($action))); + } elseif (!elgg_is_admin_logged_in() && ($CONFIG->actions[$action]['access'] === 'admin')) { + register_error(elgg_echo('actionunauthorized')); + } elseif (!elgg_is_logged_in() && ($CONFIG->actions[$action]['access'] !== 'public')) { + register_error(elgg_echo('actionloggedout')); + } else { + // Returning falsy doesn't produce an error + // We assume this will be handled in the hook itself. + if (elgg_trigger_plugin_hook('action', $action, null, true)) { + if (!include($CONFIG->actions[$action]['file'])) { + register_error(elgg_echo('actionnotfound', array($action))); } - } else { - register_error(elgg_echo('actionunauthorized')); } - } else { - register_error(sprintf(elgg_echo('actionundefined'), $action)); } + $forwarder = empty($forwarder) ? REFERER : $forwarder; forward($forwarder); } /** * Registers an action. * - * Actions are registered to a single file in the system and are executed - * either by the URL http://elggsite.org/action/action_name or by calling - * {@link action()}. + * Actions are registered to a script in the system and are executed + * either by the URL http://elggsite.org/action/action_name/. * - * $file must be the full path of the file to register, or a path relative + * $filename must be the full path of the file to register, or a path relative * to the core actions/ dir. * * Actions should be namedspaced for your plugin. Example: * <code> - * register_action('myplugin/save_settings', ...); + * elgg_register_action('myplugin/save_settings', ...); * </code> * - * @tip Put action files under the actions/ directory of your plugin. + * @tip Put action files under the actions/<plugin_name> directory of your plugin. * - * @tip You don't need to include engine/start.php, call {@link gatekeeper()}, - * or call {@link admin_gatekeeper()}. + * @tip You don't need to include engine/start.php in your action files. * * @internal Actions are saved in $CONFIG->actions as an array in the form: * <code> * array( * 'file' => '/location/to/file.php', - * 'public' => BOOL If false, user must be logged in. - * 'admin' => BOOL If true, user must be admin (implies plugin = false) + * 'access' => 'public', 'logged_in', or 'admin' * ) * </code> * - * @param string $action The name of the action (eg "register", "account/settings/save") - * @param boolean $public Can this action be accessed by people not logged into the system? - * @param string $filename Optionally, the filename where this action is located - * @param boolean $admin_only Whether this action is only available to admin users. + * @param string $action The name of the action (eg "register", "account/settings/save") + * @param string $filename Optionally, the filename where this action is located. If not specified, + * will assume the action is in elgg/actions/<action>.php + * @param string $access Who is allowed to execute this action: public, logged_in, admin. + * (default: logged_in) * * @see action() * @see http://docs.elgg.org/Actions * - * @return true + * @return bool */ -function register_action($action, $public = false, $filename = "", $admin_only = false) { +function elgg_register_action($action, $filename = "", $access = 'logged_in') { global $CONFIG; // plugins are encouraged to call actions with a trailing / to prevent 301 @@ -177,23 +163,57 @@ function register_action($action, $public = false, $filename = "", $admin_only = $CONFIG->actions[$action] = array( 'file' => $filename, - 'public' => $public, - 'admin' => $admin_only + 'access' => $access, ); return true; } /** + * Unregisters an action + * + * @param string $action Action name + * @return bool + * @since 1.8.1 + */ +function elgg_unregister_action($action) { + global $CONFIG; + + if (isset($CONFIG->actions[$action])) { + unset($CONFIG->actions[$action]); + return true; + } else { + return false; + } +} + +/** + * Is the token timestamp within acceptable range? + * + * @param int $ts timestamp from the CSRF token + * + * @return bool + */ +function _elgg_validate_token_timestamp($ts) { + $action_token_timeout = elgg_get_config('action_token_timeout'); + // default is 2 hours + $timeout = ($action_token_timeout !== null) ? $action_token_timeout : 2; + + $hour = 60 * 60; + $timeout = $timeout * $hour; + $now = time(); + + // Validate time to ensure its not crazy + return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout)); +} + +/** * Validate an action token. * - * Calls to actions will automatically validate tokens. - * If tokens are not present or invalid, the action will be - * denied and the user will be redirected to the front page. + * Calls to actions will automatically validate tokens. If tokens are not + * present or invalid, the action will be denied and the user will be redirected. * * Plugin authors should never have to manually validate action tokens. * - * @access private - * * @param bool $visibleerrors Emit {@link register_error()} errors on failure? * @param mixed $token The token to test against. Default: $_REQUEST['__elgg_token'] * @param mixed $ts The time stamp to test against. Default: $_REQUEST['__elgg_ts'] @@ -201,6 +221,7 @@ function register_action($action, $public = false, $filename = "", $admin_only = * @return bool * @see generate_action_token() * @link http://docs.elgg.org/Actions/Tokens + * @access private */ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL) { if (!$token) { @@ -215,20 +236,17 @@ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL) if (($token) && ($ts) && ($session_id)) { // generate token, check with input and forward if invalid - $generated_token = generate_action_token($ts); + $required_token = generate_action_token($ts); // Validate token - if ($token == $generated_token) { - $hour = 60 * 60; - $now = time(); - - // Validate time to ensure its not crazy - if (($ts > $now - $hour) && ($ts < $now + $hour)) { + if ($token == $required_token) { + + if (_elgg_validate_token_timestamp($ts)) { // We have already got this far, so unless anything - // else says something to the contry we assume we're ok + // else says something to the contrary we assume we're ok $returnval = true; - $returnval = trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', array( + $returnval = elgg_trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', array( 'token' => $token, 'time' => $ts ), $returnval); @@ -239,37 +257,78 @@ function validate_action_token($visibleerrors = TRUE, $token = NULL, $ts = NULL) register_error(elgg_echo('actiongatekeeper:pluginprevents')); } } else if ($visibleerrors) { - register_error(elgg_echo('actiongatekeeper:timeerror')); + // this is necessary because of #5133 + if (elgg_is_xhr()) { + register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url()))); + } else { + register_error(elgg_echo('actiongatekeeper:timeerror')); + } } } else if ($visibleerrors) { - register_error(elgg_echo('actiongatekeeper:tokeninvalid')); + // this is necessary because of #5133 + if (elgg_is_xhr()) { + register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url()))); + } else { + register_error(elgg_echo('actiongatekeeper:tokeninvalid')); + } + } + } else { + if (! empty($_SERVER['CONTENT_LENGTH']) && empty($_POST)) { + // The size of $_POST or uploaded file has exceed the size limit + $error_msg = elgg_trigger_plugin_hook('action_gatekeeper:upload_exceeded_msg', 'all', array( + 'post_size' => $_SERVER['CONTENT_LENGTH'], + 'visible_errors' => $visibleerrors, + ), elgg_echo('actiongatekeeper:uploadexceeded')); + } else { + $error_msg = elgg_echo('actiongatekeeper:missingfields'); + } + if ($visibleerrors) { + register_error($error_msg); } - } else if ($visibleerrors) { - register_error(elgg_echo('actiongatekeeper:missingfields')); } return FALSE; } /** -* Validates the presence of action tokens. -* -* This function is called for all actions. If action tokens are missing, -* the user will be forwarded to the site front page and an error emitted. -* -* This function verifies form input for security features (like a generated token), and forwards -* the page if they are invalid. -* -* @access private -* @return mixed True if valid, or redirects to front page and exists. -*/ -function action_gatekeeper() { - if (validate_action_token()) { - return TRUE; + * Validates the presence of action tokens. + * + * This function is called for all actions. If action tokens are missing, + * the user will be forwarded to the site front page and an error emitted. + * + * This function verifies form input for security features (like a generated token), + * and forwards if they are invalid. + * + * @param string $action The action being performed + * + * @return mixed True if valid or redirects. + * @access private + */ +function action_gatekeeper($action) { + if ($action === 'login') { + if (validate_action_token(false)) { + return true; + } + + $token = get_input('__elgg_token'); + $ts = (int)get_input('__elgg_ts'); + if ($token && _elgg_validate_token_timestamp($ts)) { + // The tokens are present and the time looks valid: this is probably a mismatch due to the + // login form being on a different domain. + register_error(elgg_echo('actiongatekeeper:crosssitelogin')); + + + forward('login', 'csrf'); + } + + // let the validator send an appropriate msg + validate_action_token(); + + } elseif (validate_action_token()) { + return true; } - forward(); - exit; + forward(REFERER, 'csrf'); } /** @@ -289,6 +348,7 @@ function action_gatekeeper() { * @example actions/manual_tokens.php * * @return string|false + * @access private */ function generate_action_token($timestamp) { $site_secret = get_site_secret(); @@ -304,16 +364,19 @@ function generate_action_token($timestamp) { } /** - * Initialise the site secret hash. + * Initialise the site secret (32 bytes: "z" to indicate format + 186-bit key in Base64 URL). * * Used during installation and saves as a datalist. * + * Note: Old secrets were hex encoded. + * * @return mixed The site secret hash or false * @access private * @todo Move to better file. */ function init_site_secret() { - $secret = md5(rand() . microtime()); + $secret = 'z' . ElggCrypto::getRandomString(31); + if (datalist_set('__site_secret__', $secret)) { return $secret; } @@ -340,45 +403,54 @@ function get_site_secret() { } /** - * Check if an action is registered and its file exists. + * Get the strength of the site secret + * + * @return string "strong", "moderate", or "weak" + * @access private + */ +function _elgg_get_site_secret_strength() { + $secret = get_site_secret(); + if ($secret[0] !== 'z') { + $rand_max = getrandmax(); + if ($rand_max < pow(2, 16)) { + return 'weak'; + } + if ($rand_max < pow(2, 32)) { + return 'moderate'; + } + } + return 'strong'; +} + +/** + * Check if an action is registered and its script exists. * * @param string $action Action name * - * @return BOOL - * @since 1.8 + * @return bool + * @since 1.8.0 */ -function elgg_action_exist($action) { +function elgg_action_exists($action) { global $CONFIG; return (isset($CONFIG->actions[$action]) && file_exists($CONFIG->actions[$action]['file'])); } /** - * Initialize some ajaxy actions features - */ -function actions_init() -{ - register_action('security/refreshtoken', TRUE); - - elgg_view_register_simplecache('js/languages/en'); - - register_plugin_hook('action', 'all', 'ajax_action_hook'); - register_plugin_hook('forward', 'all', 'ajax_forward_hook'); -} - -/** * Checks whether the request was requested via ajax - * + * * @return bool whether page was requested via ajax + * @since 1.8.0 */ function elgg_is_xhr() { - return isset($_SERVER['HTTP_X_REQUESTED_WITH']) - && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; + return isset($_SERVER['HTTP_X_REQUESTED_WITH']) + && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest' || + get_input('X-Requested-With') === 'XMLHttpRequest'; } /** * Catch calls to forward() in ajax request and force an exit. - * + * * Forces response is json of the following form: * <pre> * { @@ -392,18 +464,29 @@ function elgg_is_xhr() { * } * </pre> * where "system_messages" is all message registers at the point of forwarding - * + * * @param string $hook - * @param string $type + * @param string $type * @param string $reason * @param array $params - * + * @return void + * @access private */ function ajax_forward_hook($hook, $type, $reason, $params) { if (elgg_is_xhr()) { + // always pass the full structure to avoid boilerplate JS code. + $params = array( + 'output' => '', + 'status' => 0, + 'system_messages' => array( + 'error' => array(), + 'success' => array() + ) + ); + //grab any data echo'd in the action $output = ob_get_clean(); - + //Avoid double-encoding in case data is json $json = json_decode($output); if (isset($json)) { @@ -411,17 +494,29 @@ function ajax_forward_hook($hook, $type, $reason, $params) { } else { $params['output'] = $output; } - + //Grab any system messages so we can inject them via ajax too - $params['system_messages'] = system_messages(NULL, ""); - - if (isset($params['system_messages']['errors'])) { + $system_messages = system_messages(NULL, ""); + + if (isset($system_messages['success'])) { + $params['system_messages']['success'] = $system_messages['success']; + } + + if (isset($system_messages['error'])) { + $params['system_messages']['error'] = $system_messages['error']; $params['status'] = -1; + } + + // Check the requester can accept JSON responses, if not fall back to + // returning JSON in a plain-text response. Some libraries request + // JSON in an invisible iframe which they then read from the iframe, + // however some browsers will not accept the JSON MIME type. + if (stripos($_SERVER['HTTP_ACCEPT'], 'application/json') === FALSE) { + header("Content-type: text/plain"); } else { - $params['status'] = 0; + header("Content-type: application/json"); } - - header("Content-type: application/json"); + echo json_encode($params); exit; } @@ -429,6 +524,8 @@ function ajax_forward_hook($hook, $type, $reason, $params) { /** * Buffer all output echo'd directly in the action for inclusion in the returned JSON. + * @return void + * @access private */ function ajax_action_hook() { if (elgg_is_xhr()) { @@ -436,4 +533,17 @@ function ajax_action_hook() { } } -register_elgg_event_handler('init', 'system', 'actions_init');
\ No newline at end of file +/** + * Initialize some ajaxy actions features + * @access private + */ +function actions_init() { + elgg_register_action('security/refreshtoken', '', 'public'); + + elgg_register_simplecache_view('js/languages/en'); + + elgg_register_plugin_hook_handler('action', 'all', 'ajax_action_hook'); + elgg_register_plugin_hook_handler('forward', 'all', 'ajax_forward_hook'); +} + +elgg_register_event_handler('init', 'system', 'actions_init'); diff --git a/engine/lib/admin.php b/engine/lib/admin.php index b760adabe..f36f29668 100644 --- a/engine/lib/admin.php +++ b/engine/lib/admin.php @@ -1,159 +1,443 @@ <?php /** * Elgg admin functions. - * Functions for adding and manipulating options on the admin panel. * - * @package Elgg - * @subpackage Core - */ - -/** - * Register an admin page with the admin panel. - * This function extends the view "admin/main" with the provided view. - * This view should provide a description and either a control or a link to. + * Admin menu items + * Elgg has a convenience function for adding menu items to the sidebar of the + * admin area. @see elgg_register_admin_menu_item() * - * Usage: - * - To add a control to the main admin panel then extend admin/main - * - To add a control to a new page create a page which renders a view admin/subpage - * (where subpage is your new page - - * nb. some pages already exist that you can extend), extend the main view to point to it, - * and add controls to your new view. + * Admin pages + * Plugins no not need to provide their own page handler to add a page to the + * admin area. A view placed at admin/<section>/<subsection> can be access + * at http://example.org/admin/<section>/<subsection>. The title of the page + * will be elgg_echo('admin:<section>:<subsection>'). For an example of how to + * add a page to the admin area, see the diagnostics plugin. * - * At the moment this is essentially a wrapper around elgg_extend_view(). + * Admin notices + * System messages (success and error messages) are used in both the main site + * and the admin area. There is a special presistent message for the admin area + * called an admin notice. It should be used when a plugin requires an + * administrator to take an action. An example is the categories plugin + * requesting that the administrator set site categories after the plugin has + * been activated. @see elgg_add_admin_notice() * - * @param string $new_admin_view The view associated with the control you're adding - * @param string $view The view to extend, by default this is 'admin/main'. - * @param int $priority Optional priority to govern the appearance in the list. * - * @return void + * @package Elgg.Core + * @subpackage Admin */ -function extend_elgg_admin_page($new_admin_view, $view = 'admin/main', $priority = 500) { - elgg_deprecated_notice('extend_elgg_admin_page() does nothing. Extend admin views manually.', 1.8); -} /** - * Calculate the plugin settings submenu. - * This is done in a separate function called from the admin - * page handler because of performance concerns. + * Get the admin users * - * @return void + * @param array $options Options array, @see elgg_get_entities() for parameters + * + * @return mixed Array of admin users or false on failure. If a count, returns int. + * @since 1.8.0 */ -function elgg_admin_add_plugin_settings_sidemenu() { +function elgg_get_admins(array $options = array()) { global $CONFIG; - if (!$installed_plugins = get_installed_plugins()) { - // nothing added because no items - return FALSE; + if (isset($options['joins'])) { + if (!is_array($options['joins'])) { + $options['joins'] = array($options['joins']); + } + $options['joins'][] = "join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid"; + } else { + $options['joins'] = array("join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid"); } - $parent_item = array( - 'text' => elgg_echo('admin:plugin_settings'), - 'id' => 'admin:plugin_settings' - ); + if (isset($options['wheres'])) { + if (!is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } + $options['wheres'][] = "u.admin = 'yes'"; + } else { + $options['wheres'][] = "u.admin = 'yes'"; + } - elgg_add_submenu_item($parent_item, 'admin'); + return elgg_get_entities($options); +} - foreach ($installed_plugins as $plugin_id => $info) { - if (!$info['active']) { - continue; +/** + * Write a persistent message to the admin view. + * Useful to alert the admin to take a certain action. + * The id is a unique ID that can be cleared once the admin + * completes the action. + * + * eg: add_admin_notice('twitter_services_no_api', + * 'Before your users can use Twitter services on this site, you must set up + * the Twitter API key in the <a href="link">Twitter Services Settings</a>'); + * + * @param string $id A unique ID that your plugin can remember + * @param string $message Body of the message + * + * @return bool + * @since 1.8.0 + */ +function elgg_add_admin_notice($id, $message) { + if ($id && $message) { + if (elgg_admin_notice_exists($id)) { + return false; } - if (elgg_view_exists("settings/{$plugin_id}/edit")) { - $item = array( - 'text' => $info['manifest']['name'], - 'href' => "pg/admin/plugin_settings/$plugin_id", - 'parent_id' => 'admin:plugin_settings' - ); + // need to handle when no one is logged in + $old_ia = elgg_set_ignore_access(true); + + $admin_notice = new ElggObject(); + $admin_notice->subtype = 'admin_notice'; + // admins can see ACCESS_PRIVATE but no one else can. + $admin_notice->access_id = ACCESS_PRIVATE; + $admin_notice->admin_notice_id = $id; + $admin_notice->description = $message; + + $result = $admin_notice->save(); + + elgg_set_ignore_access($old_ia); + + return (bool)$result; + } + + return false; +} + +/** + * Remove an admin notice by ID. + * + * eg In actions/twitter_service/save_settings: + * if (is_valid_twitter_api_key()) { + * delete_admin_notice('twitter_services_no_api'); + * } + * + * @param string $id The unique ID assigned in add_admin_notice() + * + * @return bool + * @since 1.8.0 + */ +function elgg_delete_admin_notice($id) { + if (!$id) { + return FALSE; + } + $result = TRUE; + $notices = elgg_get_entities_from_metadata(array( + 'metadata_name' => 'admin_notice_id', + 'metadata_value' => $id + )); - elgg_add_submenu_item($item, 'admin'); + if ($notices) { + // in case a bad plugin adds many, let it remove them all at once. + foreach ($notices as $notice) { + $result = ($result && $notice->delete()); } + return $result; } + return FALSE; +} + +/** + * Get admin notices. An admin must be logged in since the notices are private. + * + * @param int $limit Limit + * + * @return array Array of admin notices + * @since 1.8.0 + */ +function elgg_get_admin_notices($limit = 10) { + return elgg_get_entities_from_metadata(array( + 'type' => 'object', + 'subtype' => 'admin_notice', + 'limit' => $limit + )); +} + +/** + * Check if an admin notice is currently active. + * + * @param string $id The unique ID used to register the notice. + * + * @return bool + * @since 1.8.0 + */ +function elgg_admin_notice_exists($id) { + $old_ia = elgg_set_ignore_access(true); + $notice = elgg_get_entities_from_metadata(array( + 'type' => 'object', + 'subtype' => 'admin_notice', + 'metadata_name_value_pair' => array('name' => 'admin_notice_id', 'value' => $id) + )); + elgg_set_ignore_access($old_ia); + + return ($notice) ? TRUE : FALSE; } /** * Add an admin area section or child section. - * This is a wrapper for elgg_add_admin_item(array(...), 'admin'). + * This is a wrapper for elgg_register_menu_item(). * * Used in conjuction with http://elgg.org/admin/section_id/child_section style - * page handler. + * page handler. See the documentation at the top of this file for more details + * on that. + * + * The text of the menu item is obtained from elgg_echo(admin:$parent_id:$menu_id) + * + * This function handles registering the parent if it has not been registered. * - * @param string $section_id The Unique ID of section - * @param string $section_title Human readable section title. - * @param string $parent_id If a child section, the parent section id. + * @param string $section The menu section to add to + * @param string $menu_id The unique ID of section + * @param string $parent_id If a child section, the parent section id + * @param int $priority The menu item priority * * @return bool + * @since 1.8.0 */ -function elgg_add_admin_submenu_item($section_id, $section_title, $parent_id = NULL) { - global $CONFIG; +function elgg_register_admin_menu_item($section, $menu_id, $parent_id = NULL, $priority = 100) { + + // make sure parent is registered + if ($parent_id && !elgg_is_menu_item_registered('page', $parent_id)) { + elgg_register_admin_menu_item($section, $parent_id); + } // in the admin section parents never have links if ($parent_id) { - $href = "pg/admin/$parent_id/$section_id"; - } elseif ($section_id == 'overview') { - $href = "pg/admin/$section_id"; - + $href = "admin/$parent_id/$menu_id"; } else { $href = NULL; } - $item = array( - 'text' => $section_title, - 'href' => $href, - 'id' => $section_id, - 'parent_id' => $parent_id - ); + $name = $menu_id; + if ($parent_id) { + $name = "$parent_id:$name"; + } - return elgg_add_submenu_item($item, 'admin'); + return elgg_register_menu_item('page', array( + 'name' => $name, + 'href' => $href, + 'text' => elgg_echo("admin:$name"), + 'context' => 'admin', + 'parent_name' => $parent_id, + 'priority' => $priority, + 'section' => $section + )); } /** - * Initialise the admin page. - * + * Initialize the admin backend. * @return void + * @access private */ function admin_init() { - register_action('admin/user/ban', FALSE, "", TRUE); - register_action('admin/user/unban', FALSE, "", TRUE); - register_action('admin/user/delete', FALSE, "", TRUE); - register_action('admin/user/resetpassword', FALSE, "", TRUE); - register_action('admin/user/makeadmin', FALSE, "", TRUE); - register_action('admin/user/removeadmin', FALSE, "", TRUE); + elgg_register_action('admin/user/ban', '', 'admin'); + elgg_register_action('admin/user/unban', '', 'admin'); + elgg_register_action('admin/user/delete', '', 'admin'); + elgg_register_action('admin/user/resetpassword', '', 'admin'); + elgg_register_action('admin/user/makeadmin', '', 'admin'); + elgg_register_action('admin/user/removeadmin', '', 'admin'); + + elgg_register_action('admin/site/update_basic', '', 'admin'); + elgg_register_action('admin/site/update_advanced', '', 'admin'); + elgg_register_action('admin/site/flush_cache', '', 'admin'); + elgg_register_action('admin/site/unlock_upgrade', '', 'admin'); + elgg_register_action('admin/site/regenerate_secret', '', 'admin'); + + elgg_register_action('admin/menu/save', '', 'admin'); + + elgg_register_action('admin/delete_admin_notice', '', 'admin'); + + elgg_register_action('profile/fields/reset', '', 'admin'); + elgg_register_action('profile/fields/add', '', 'admin'); + elgg_register_action('profile/fields/edit', '', 'admin'); + elgg_register_action('profile/fields/delete', '', 'admin'); + elgg_register_action('profile/fields/reorder', '', 'admin'); + + elgg_register_simplecache_view('css/admin'); + elgg_register_simplecache_view('js/admin'); + $url = elgg_get_simplecache_url('js', 'admin'); + elgg_register_js('elgg.admin', $url); + elgg_register_js('jquery.jeditable', 'vendors/jquery/jquery.jeditable.mini.js'); + + // administer + // dashboard + elgg_register_menu_item('page', array( + 'name' => 'dashboard', + 'href' => 'admin/dashboard', + 'text' => elgg_echo('admin:dashboard'), + 'context' => 'admin', + 'priority' => 10, + 'section' => 'administer' + )); + // statistics + elgg_register_admin_menu_item('administer', 'statistics', null, 20); + elgg_register_admin_menu_item('administer', 'overview', 'statistics'); + elgg_register_admin_menu_item('administer', 'server', 'statistics'); - register_action('admin/site/update_basic', FALSE, "", TRUE); - register_action('admin/site/update_advanced', FALSE, "", TRUE); + // users + elgg_register_admin_menu_item('administer', 'users', null, 20); + elgg_register_admin_menu_item('administer', 'online', 'users', 10); + elgg_register_admin_menu_item('administer', 'admins', 'users', 20); + elgg_register_admin_menu_item('administer', 'newest', 'users', 30); + elgg_register_admin_menu_item('administer', 'add', 'users', 40); - register_action('admin/menu_items', FALSE, "", TRUE); + // configure + // plugins + elgg_register_menu_item('page', array( + 'name' => 'plugins', + 'href' => 'admin/plugins', + 'text' => elgg_echo('admin:plugins'), + 'context' => 'admin', + 'priority' => 75, + 'section' => 'configure' + )); - register_action('admin/plugins/simple_update_states', FALSE, '', TRUE); + // settings + elgg_register_admin_menu_item('configure', 'appearance', null, 50); + elgg_register_admin_menu_item('configure', 'settings', null, 100); + elgg_register_admin_menu_item('configure', 'basic', 'settings', 10); + elgg_register_admin_menu_item('configure', 'advanced', 'settings', 20); + elgg_register_admin_menu_item('configure', 'advanced/site_secret', 'settings', 25); + elgg_register_admin_menu_item('configure', 'menu_items', 'appearance', 30); + elgg_register_admin_menu_item('configure', 'profile_fields', 'appearance', 40); + // default widgets is added via an event handler elgg_default_widgets_init() in widgets.php + // because it requires additional setup. + + // plugin settings are added in elgg_admin_add_plugin_settings_menu() via the admin page handler + // for performance reasons. + + // we want plugin settings menu items to be sorted alphabetical + if (elgg_in_context('admin')) { + elgg_register_plugin_hook_handler('prepare', 'menu:page', 'elgg_admin_sort_page_menu'); + } - // admin area overview and basic site settings - elgg_add_admin_submenu_item('overview', elgg_echo('admin:overview')); + if (elgg_is_admin_logged_in()) { + elgg_register_menu_item('topbar', array( + 'name' => 'administration', + 'href' => 'admin', + 'text' => elgg_view_icon('settings') . elgg_echo('admin'), + 'priority' => 100, + 'section' => 'alt', + )); + } + + // widgets + $widgets = array('online_users', 'new_users', 'content_stats', 'admin_welcome', 'control_panel'); + foreach ($widgets as $widget) { + elgg_register_widget_type( + $widget, + elgg_echo("admin:widget:$widget"), + elgg_echo("admin:widget:$widget:help"), + 'admin' + ); + } - elgg_add_admin_submenu_item('site', elgg_echo('admin:site')); - elgg_add_admin_submenu_item('basic', elgg_echo('admin:site:basic'), 'site'); - elgg_add_admin_submenu_item('advanced', elgg_echo('admin:site:advanced'), 'site'); + // automatic adding of widgets for admin + elgg_register_event_handler('make_admin', 'user', 'elgg_add_admin_widgets'); - // appearance - elgg_add_admin_submenu_item('appearance', elgg_echo('admin:appearance')); + elgg_register_page_handler('admin', 'admin_page_handler'); + elgg_register_page_handler('admin_plugin_screenshot', 'admin_plugin_screenshot_page_handler'); + elgg_register_page_handler('admin_plugin_text_file', 'admin_markdown_page_handler'); +} - //elgg_add_admin_submenu_item('basic', elgg_echo('admin:appearance'), 'appearance'); - elgg_add_admin_submenu_item('menu_items', elgg_echo('admin:menu_items'), 'appearance'); +/** + * Create the plugin settings page menu. + * + * This is done in a separate function called from the admin + * page handler because of performance concerns. + * + * @return void + * @access private + * @since 1.8.0 + */ +function elgg_admin_add_plugin_settings_menu() { - // users - elgg_add_admin_submenu_item('users', elgg_echo('admin:users')); - elgg_add_admin_submenu_item('online', elgg_echo('admin:users:online'), 'users'); - elgg_add_admin_submenu_item('newest', elgg_echo('admin:users:newest'), 'users'); - elgg_add_admin_submenu_item('add', elgg_echo('admin:users:add'), 'users'); + $active_plugins = elgg_get_plugins('active'); + if (!$active_plugins) { + // nothing added because no items + return; + } - // plugins - elgg_add_admin_submenu_item('plugins', elgg_echo('admin:plugins')); - elgg_add_admin_submenu_item('simple', elgg_echo('admin:plugins:simple'), 'plugins'); - elgg_add_admin_submenu_item('advanced', elgg_echo('admin:plugins:advanced'), 'plugins'); + foreach ($active_plugins as $plugin) { + $plugin_id = $plugin->getID(); + $settings_view_old = 'settings/' . $plugin_id . '/edit'; + $settings_view_new = 'plugins/' . $plugin_id . '/settings'; + if (elgg_view_exists($settings_view_new) || elgg_view_exists($settings_view_old)) { + elgg_register_menu_item('page', array( + 'name' => $plugin_id, + 'href' => "admin/plugin_settings/$plugin_id", + 'text' => $plugin->getManifest()->getName(), + 'parent_name' => 'settings', + 'context' => 'admin', + 'section' => 'configure', + )); + } + } +} + +/** + * Sort the plugin settings menu items + * + * @param string $hook + * @param string $type + * @param array $return + * @param array $params + * + * @return void + * @since 1.8.0 + * @access private + */ +function elgg_admin_sort_page_menu($hook, $type, $return, $params) { + $configure_items = $return['configure']; + /* @var ElggMenuItem[] $configure_items */ + foreach ($configure_items as $menu_item) { + if ($menu_item->getName() == 'settings') { + $settings = $menu_item; + } + } - // handled in the admin sidemenu so we don't have to generate this on every page load. - //elgg_add_admin_submenu_item('plugin_settings', elgg_echo('admin:plugin_settings')); + // keep the basic and advanced settings at the top + /* @var ElggMenuItem $settings */ + $children = $settings->getChildren(); + $site_settings = array_splice($children, 0, 2); + usort($children, array('ElggMenuBuilder', 'compareByText')); + array_splice($children, 0, 0, $site_settings); + $settings->setChildren($children); +} - register_page_handler('admin', 'admin_settings_page_handler'); +/** + * Handles any set up required for administration pages + * + * @return void + * @access private + */ +function admin_pagesetup() { + if (elgg_in_context('admin')) { + $url = elgg_get_simplecache_url('css', 'admin'); + elgg_register_css('elgg.admin', $url); + elgg_load_css('elgg.admin'); + elgg_unregister_css('elgg'); + + // setup footer menu + elgg_register_menu_item('admin_footer', array( + 'name' => 'faq', + 'text' => elgg_echo('admin:footer:faq'), + 'href' => 'http://docs.elgg.org/wiki/Category:Administration_FAQ', + )); + + elgg_register_menu_item('admin_footer', array( + 'name' => 'manual', + 'text' => elgg_echo('admin:footer:manual'), + 'href' => 'http://docs.elgg.org/wiki/Administration_Manual', + )); + + elgg_register_menu_item('admin_footer', array( + 'name' => 'community_forums', + 'text' => elgg_echo('admin:footer:community_forums'), + 'href' => 'http://community.elgg.org/groups/all/', + )); + + elgg_register_menu_item('admin_footer', array( + 'name' => 'blog', + 'text' => elgg_echo('admin:footer:blog'), + 'href' => 'http://blog.elgg.org/', + )); + } } /** @@ -161,18 +445,22 @@ function admin_init() { * * @param array $page Array of pages * - * @return void + * @return bool + * @access private */ -function admin_settings_page_handler($page) { - global $CONFIG; +function admin_page_handler($page) { admin_gatekeeper(); - elgg_admin_add_plugin_settings_sidemenu(); + elgg_admin_add_plugin_settings_menu(); elgg_set_context('admin'); - // default to overview + elgg_unregister_css('elgg'); + elgg_load_js('elgg.admin'); + elgg_load_js('jquery.jeditable'); + + // default to dashboard if (!isset($page[0]) || empty($page[0])) { - $page = array('overview'); + $page = array('dashboard'); } // was going to fix this in the page_handler() function but @@ -184,132 +472,192 @@ function admin_settings_page_handler($page) { $vars = array('page' => $page); // special page for plugin settings since we create the form for them - if ($page[0] == 'plugin_settings' && isset($page[1]) - && elgg_view_exists("settings/{$page[1]}/edit")) { + if ($page[0] == 'plugin_settings') { + if (isset($page[1]) && (elgg_view_exists("settings/{$page[1]}/edit") || + elgg_view_exists("plugins/{$page[1]}/settings"))) { + + $view = 'admin/plugin_settings'; + $plugin = elgg_get_plugin_from_id($page[1]); + $vars['plugin'] = $plugin; - $view = '/admin/components/plugin_settings'; - $vars['plugin'] = $page[1]; - $vars['entity'] = find_plugin_settings($page[1]); - $title = elgg_echo("admin:plugin_settings:{$page[1]}"); + $title = elgg_echo("admin:{$page[0]}"); + } else { + forward('', '404'); + } } else { $view = 'admin/' . implode('/', $page); - $title = elgg_echo('admin:' . implode(':', $page)); + $title = elgg_echo("admin:{$page[0]}"); + if (count($page) > 1) { + $title .= ' : ' . elgg_echo('admin:' . implode(':', $page)); + } } - // allow a place to store helper views outside of the web-accessible views + // gets content and prevents direct access to 'components' views if ($page[0] == 'components' || !($content = elgg_view($view, $vars))) { $title = elgg_echo('admin:unknown_section'); $content = elgg_echo('admin:unknown_section'); } - $notices_html = ''; - if ($notices = elgg_get_admin_notices()) { - foreach ($notices as $notice) { - $notices_html .= elgg_view_entity($notice); - } - - $content = "<div class=\"admin_notices\">$notices_html</div>$content"; - } - - $body = elgg_view_layout('administration', $content); - echo elgg_view_page($title, $body, 'page_shells/admin'); + $body = elgg_view_layout('admin', array('content' => $content, 'title' => $title)); + echo elgg_view_page($title, $body, 'admin'); + return true; } /** - * Write a persistent message to the admin view. - * Useful to alert the admin to take a certain action. - * The id is a unique ID that can be cleared once the admin - * completes the action. - * - * eg: add_admin_notice('twitter_services_no_api', - * 'Before your users can use Twitter services on this site, you must set up - * the Twitter API key in the <a href="link">Twitter Services Settings</a>'); - * - * @param string $id A unique ID that your plugin can remember - * @param string $message Body of the message + * Serves up screenshots for plugins from + * admin_plugin_screenshot/<plugin_id>/<size>/<ss_name>.<ext> * - * @return boo + * @param array $pages The pages array + * @return bool + * @access private */ -function elgg_add_admin_notice($id, $message) { - if ($id && $message) { - $admin_notice = new ElggObject(); - $admin_notice->subtype = 'admin_notice'; - // admins can see ACCESS_PRIVATE but no one else can. - $admin_notice->access_id = ACCESS_PRIVATE; - $admin_notice->admin_notice_id = $id; - $admin_notice->description = $message; +function admin_plugin_screenshot_page_handler($pages) { + // only admins can use this for security + admin_gatekeeper(); - return $admin_notice->save(); + $plugin_id = elgg_extract(0, $pages); + // only thumbnail or full. + $size = elgg_extract(1, $pages, 'thumbnail'); + + // the rest of the string is the filename + $filename_parts = array_slice($pages, 2); + $filename = implode('/', $filename_parts); + $filename = sanitise_filepath($filename, false); + + $plugin = new ElggPlugin($plugin_id); + if (!$plugin) { + $file = elgg_get_root_path() . '_graphics/icons/default/medium.png'; + } else { + $file = $plugin->getPath() . $filename; + if (!file_exists($file)) { + $file = elgg_get_root_path() . '_graphics/icons/default/medium.png'; + } } - return FALSE; -} + header("Content-type: image/jpeg"); + // resize to 100x100 for thumbnails + switch ($size) { + case 'thumbnail': + echo get_resized_image_from_existing_file($file, 100, 100, true); + break; + + case 'full': + default: + echo file_get_contents($file); + break; + } + return true; +} /** - * Remove an admin notice by ID. + * Formats and serves out markdown files from plugins. * - * eg In actions/twitter_service/save_settings: - * if (is_valid_twitter_api_key()) { - * delete_admin_notice('twitter_services_no_api'); - * } + * URLs in format like admin_plugin_text_file/<plugin_id>/filename.ext * - * @param string $id The unique ID assigned in add_admin_notice() + * The only valid files are: + * * README.txt + * * CHANGES.txt + * * INSTALL.txt + * * COPYRIGHT.txt + * * LICENSE.txt * + * @param array $pages * @return bool + * @access private */ -function elgg_delete_admin_notice($id) { - if (!$id) { - return FALSE; +function admin_markdown_page_handler($pages) { + admin_gatekeeper(); + + elgg_set_context('admin'); + + elgg_unregister_css('elgg'); + elgg_load_js('elgg.admin'); + elgg_load_js('jquery.jeditable'); + elgg_load_library('elgg:markdown'); + + $plugin_id = elgg_extract(0, $pages); + $plugin = elgg_get_plugin_from_id($plugin_id); + $filename = elgg_extract(1, $pages); + + $error = false; + if (!$plugin) { + $error = elgg_echo('admin:plugins:markdown:unknown_plugin'); + $body = elgg_view_layout('admin', array('content' => $error, 'title' => $error)); + echo elgg_view_page($error, $body, 'admin'); + return true; } - $result = TRUE; - $notices = elgg_get_entities_from_metadata(array( - 'metadata_name' => 'admin_notice_id', - 'metadata_value' => $id - )); - if ($notices) { - // in case a bad plugin adds many, let it remove them all at once. - foreach ($notices as $notice) { - $result = ($result && $notice->delete()); - } - return $result; + $text_files = $plugin->getAvailableTextFiles(); + + if (!array_key_exists($filename, $text_files)) { + $error = elgg_echo('admin:plugins:markdown:unknown_file'); } - return FALSE; -} -/** - * List all admin messages. - * - * @param int $limit Limit - * - * @return array List of admin notices - */ -function elgg_get_admin_notices($limit = 10) { - return elgg_get_entities_from_metadata(array( - 'type' => 'object', - 'subtype' => 'admin_notice', - 'limit' => $limit + $file = $text_files[$filename]; + $file_contents = file_get_contents($file); + + if (!$file_contents) { + $error = elgg_echo('admin:plugins:markdown:unknown_file'); + } + + if ($error) { + $title = $error; + $body = elgg_view_layout('admin', array('content' => $error, 'title' => $title)); + echo elgg_view_page($title, $body, 'admin'); + return true; + } + + $title = $plugin->getManifest()->getName() . ": $filename"; + $text = Markdown($file_contents); + + $body = elgg_view_layout('admin', array( + // setting classes here because there's no way to pass classes + // to the layout + 'content' => '<div class="elgg-markdown">' . $text . '</div>', + 'title' => $title )); + + echo elgg_view_page($title, $body, 'admin'); + return true; } /** - * Check if an admin notice is currently active. + * Adds default admin widgets to the admin dashboard. * - * @param string $id The unique ID used to register the notice. + * @param string $event + * @param string $type + * @param ElggUser $user * - * @return bool + * @return null|true + * @access private */ -function elgg_admin_notice_exists($id) { - $notice = elgg_get_entities_from_metadata(array( - 'type' => 'object', - 'subtype' => 'admin_notice', - 'metadata_name_value_pair' => array('name' => 'admin_notice_id', 'value' => $id) - )); +function elgg_add_admin_widgets($event, $type, $user) { + elgg_set_ignore_access(true); - return ($notice) ? TRUE : FALSE; + // check if the user already has widgets + if (elgg_get_widgets($user->getGUID(), 'admin')) { + return true; + } + + // In the form column => array of handlers in order, top to bottom + $adminWidgets = array( + 1 => array('control_panel', 'admin_welcome'), + 2 => array('online_users', 'new_users', 'content_stats'), + ); + + foreach ($adminWidgets as $column => $handlers) { + foreach ($handlers as $position => $handler) { + $guid = elgg_create_widget($user->getGUID(), $handler, 'admin'); + if ($guid) { + $widget = get_entity($guid); + /* @var ElggWidget $widget */ + $widget->move($column, $position); + } + } + } + elgg_set_ignore_access(false); } -// Register init functions -register_elgg_event_handler('init', 'system', 'admin_init'); -register_elgg_event_handler('pagesetup', 'system', 'admin_pagesetup'); +elgg_register_event_handler('init', 'system', 'admin_init'); +elgg_register_event_handler('pagesetup', 'system', 'admin_pagesetup', 1000); diff --git a/engine/lib/annotations.php b/engine/lib/annotations.php index 9ed9cf5f6..5e9b530de 100644 --- a/engine/lib/annotations.php +++ b/engine/lib/annotations.php @@ -13,9 +13,11 @@ * @param stdClass $row Db row result object * * @return ElggAnnotation + * @access private */ function row_to_elggannotation($row) { if (!($row instanceof stdClass)) { + // @todo should throw in this case? return $row; } @@ -23,25 +25,30 @@ function row_to_elggannotation($row) { } /** - * Get a specific annotation. + * Get a specific annotation by its id. + * If you want multiple annotation objects, use + * {@link elgg_get_annotations()}. * - * @param int $annotation_id Annotation ID + * @param int $id The id of the annotation object being retrieved. * - * @return ElggAnnotation + * @return ElggAnnotation|false */ -function get_annotation($annotation_id) { - global $CONFIG; - - $annotation_id = (int) $annotation_id; - $access = get_access_sql_suffix("a"); - - $query = "SELECT a.*, n.string as name, v.string as value" - . " from {$CONFIG->dbprefix}annotations a" - . " JOIN {$CONFIG->dbprefix}metastrings n on a.name_id = n.id" - . " JOIN {$CONFIG->dbprefix}metastrings v on a.value_id = v.id" - . " where a.id=$annotation_id and $access"; +function elgg_get_annotation_from_id($id) { + return elgg_get_metastring_based_object_from_id($id, 'annotations'); +} - return row_to_elggannotation(get_data_row($query)); +/** + * Deletes an annotation using its ID. + * + * @param int $id The annotation ID to delete. + * @return bool + */ +function elgg_delete_annotation_by_id($id) { + $annotation = elgg_get_annotation_from_id($id); + if (!$annotation) { + return false; + } + return $annotation->delete(); } /** @@ -50,14 +57,14 @@ function get_annotation($annotation_id) { * @param int $entity_guid Entity Guid * @param string $name Name of annotation * @param string $value Value of annotation - * @param string $value_type Type of value - * @param int $owner_guid Owner of annotation + * @param string $value_type Type of value (default is auto detection) + * @param int $owner_guid Owner of annotation (default is logged in user) * @param int $access_id Access level of annotation * * @return int|bool id on success or false on failure */ -function create_annotation($entity_guid, $name, $value, $value_type, -$owner_guid, $access_id = ACCESS_PRIVATE) { +function create_annotation($entity_guid, $name, $value, $value_type = '', +$owner_guid = 0, $access_id = ACCESS_PRIVATE) { global $CONFIG; $result = false; @@ -69,7 +76,7 @@ $owner_guid, $access_id = ACCESS_PRIVATE) { $owner_guid = (int)$owner_guid; if ($owner_guid == 0) { - $owner_guid = get_loggedin_userid(); + $owner_guid = elgg_get_logged_in_user_guid(); } $access_id = (int)$access_id; @@ -88,21 +95,19 @@ $owner_guid, $access_id = ACCESS_PRIVATE) { $entity = get_entity($entity_guid); - if (trigger_elgg_event('annotate', $entity->type, $entity)) { - system_log($entity, 'annotate'); - + if (elgg_trigger_event('annotate', $entity->type, $entity)) { // If ok then add it $result = insert_data("INSERT into {$CONFIG->dbprefix}annotations (entity_guid, name_id, value_id, value_type, owner_guid, time_created, access_id) VALUES ($entity_guid,'$name',$value,'$value_type', $owner_guid, $time, $access_id)"); if ($result !== false) { - $obj = get_annotation($result); - if (trigger_elgg_event('create', 'annotation', $obj)) { + $obj = elgg_get_annotation_from_id($result); + if (elgg_trigger_event('create', 'annotation', $obj)) { return $result; } else { // plugin returned false to reject annotation - delete_annotation($result); + elgg_delete_annotation_by_id($result); return FALSE; } } @@ -133,7 +138,7 @@ function update_annotation($annotation_id, $name, $value, $value_type, $owner_gu $owner_guid = (int)$owner_guid; if ($owner_guid == 0) { - $owner_guid = get_loggedin_userid(); + $owner_guid = elgg_get_logged_in_user_guid(); } $access_id = (int)$access_id; @@ -153,186 +158,158 @@ function update_annotation($annotation_id, $name, $value, $value_type, $owner_gu // If ok then add it $result = update_data("UPDATE {$CONFIG->dbprefix}annotations - set value_id='$value', value_type='$value_type', access_id=$access_id, owner_guid=$owner_guid - where id=$annotation_id and name_id='$name' and $access"); + set name_id='$name', value_id='$value', value_type='$value_type', access_id=$access_id, owner_guid=$owner_guid + where id=$annotation_id and $access"); if ($result !== false) { - $obj = get_annotation($annotation_id); - if (trigger_elgg_event('update', 'annotation', $obj)) { - return true; - } else { - // @todo add plugin hook that sends old and new annotation information before db access - delete_annotation($annotation_id); - } + // @todo add plugin hook that sends old and new annotation information before db access + $obj = elgg_get_annotation_from_id($annotation_id); + elgg_trigger_event('update', 'annotation', $obj); } return $result; } /** - * Get a list of annotations for a given object/user/annotation type. + * Returns annotations. Accepts all elgg_get_entities() options for entity + * restraints. * - * @param int|array $entity_guid GUID to return annotations of (falsey for any) - * @param string $entity_type Type of entity - * @param string $entity_subtype Subtype of entity - * @param string $name Name of annotation - * @param mixed $value Value of annotation - * @param int|array $owner_guid Owner(s) of annotation - * @param int $limit Limit - * @param int $offset Offset - * @param string $order_by Order annotations by SQL - * @param int $timelower Lower time limit - * @param int $timeupper Upper time limit - * @param int $entity_owner_guid Owner guid for the entity + * @see elgg_get_entities * - * @return array + * @param array $options Array in format: + * + * annotation_names => NULL|ARR Annotation names + * annotation_values => NULL|ARR Annotation values + * annotation_ids => NULL|ARR annotation ids + * annotation_case_sensitive => BOOL Overall Case sensitive + * annotation_owner_guids => NULL|ARR guids for annotation owners + * annotation_created_time_lower => INT Lower limit for created time. + * annotation_created_time_upper => INT Upper limit for created time. + * annotation_calculation => STR Perform the MySQL function on the annotation values returned. + * Do not confuse this "annotation_calculation" option with the + * "calculation" option to elgg_get_entities_from_annotation_calculation(). + * The "annotation_calculation" option causes this function to + * return the result of performing a mathematical calculation on + * all annotations that match the query instead of ElggAnnotation + * objects. + * See the docs for elgg_get_entities_from_annotation_calculation() + * for the proper use of the "calculation" option. + * + * + * @return ElggAnnotation[]|mixed + * @since 1.8.0 */ -function get_annotations($entity_guid = 0, $entity_type = "", $entity_subtype = "", $name = "", -$value = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "asc", $timelower = 0, -$timeupper = 0, $entity_owner_guid = 0) { - global $CONFIG; - - $timelower = (int) $timelower; - $timeupper = (int) $timeupper; +function elgg_get_annotations(array $options = array()) { - if (is_array($entity_guid)) { - if (sizeof($entity_guid) > 0) { - foreach ($entity_guid as $key => $val) { - $entity_guid[$key] = (int) $val; - } - } else { - $entity_guid = 0; - } + // @todo remove support for count shortcut - see #4393 + if (isset($options['__egefac']) && $options['__egefac']) { + unset($options['__egefac']); } else { - $entity_guid = (int)$entity_guid; - } - - $entity_type = sanitise_string($entity_type); - - if ($entity_subtype) { - if (!$entity_subtype = get_subtype_id($entity_type, $entity_subtype)) { - // requesting a non-existing subtype: return false - return FALSE; - } - } - - if ($name) { - $name = get_metastring_id($name); - - if ($name === false) { - $name = 0; - } - } - if ($value != "") { - $value = get_metastring_id($value); - } - - if (is_array($owner_guid)) { - if (sizeof($owner_guid) > 0) { - foreach ($owner_guid as $key => $val) { - $owner_guid[$key] = (int) $val; - } - } else { - $owner_guid = 0; - } - } else { - $owner_guid = (int)$owner_guid; - } - - if (is_array($entity_owner_guid)) { - if (sizeof($entity_owner_guid) > 0) { - foreach ($entity_owner_guid as $key => $val) { - $entity_owner_guid[$key] = (int) $val; - } - } else { - $entity_owner_guid = 0; - } - } else { - $entity_owner_guid = (int)$entity_owner_guid; - } - - $limit = (int)$limit; - $offset = (int)$offset; - if ($order_by == 'asc') { - $order_by = "a.time_created asc"; - } - - if ($order_by == 'desc') { - $order_by = "a.time_created desc"; - } - - $where = array(); - - if ($entity_guid != 0 && !is_array($entity_guid)) { - $where[] = "a.entity_guid=$entity_guid"; - } else if (is_array($entity_guid)) { - $where[] = "a.entity_guid in (" . implode(",", $entity_guid) . ")"; - } - - if ($entity_type != "") { - $where[] = "e.type='$entity_type'"; - } - - if ($entity_subtype != "") { - $where[] = "e.subtype='$entity_subtype'"; - } + // support shortcut of 'count' => true for 'annotation_calculation' => 'count' + if (isset($options['count']) && $options['count']) { + $options['annotation_calculation'] = 'count'; + unset($options['count']); + } + } + + $options['metastring_type'] = 'annotations'; + return elgg_get_metastring_based_objects($options); +} - if ($owner_guid != 0 && !is_array($owner_guid)) { - $where[] = "a.owner_guid=$owner_guid"; - } else { - if (is_array($owner_guid)) { - $where[] = "a.owner_guid in (" . implode(",", $owner_guid) . ")"; - } +/** + * Deletes annotations based on $options. + * + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! + * This requires at least one constraint: annotation_owner_guid(s), + * annotation_name(s), annotation_value(s), or guid(s) must be set. + * + * @param array $options An options array. {@See elgg_get_annotations()} + * @return bool|null true on success, false on failure, null if no annotations to delete. + * @since 1.8.0 + */ +function elgg_delete_annotations(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'annotations')) { + return false; } - if ($entity_owner_guid != 0 && !is_array($entity_owner_guid)) { - $where[] = "e.owner_guid=$entity_owner_guid"; - } else { - if (is_array($entity_owner_guid)) { - $where[] = "e.owner_guid in (" . implode(",", $entity_owner_guid) . ")"; - } - } + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); +} - if ($name !== "") { - $where[] = "a.name_id='$name'"; +/** + * Disables annotations based on $options. + * + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! + * + * @param array $options An options array. {@See elgg_get_annotations()} + * @return bool|null true on success, false on failure, null if no annotations disabled. + * @since 1.8.0 + */ +function elgg_disable_annotations(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'annotations')) { + return false; } + + // if we can see hidden (disabled) we need to use the offset + // otherwise we risk an infinite loop if there are more than 50 + $inc_offset = access_get_show_hidden_status(); - if ($value != "") { - $where[] = "a.value_id='$value'"; - } + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset); +} - if ($timelower) { - $where[] = "a.time_created >= {$timelower}"; +/** + * Enables annotations based on $options. + * + * @warning Unlike elgg_get_annotations() this will not accept an empty options array! + * + * @warning In order to enable annotations, you must first use + * {@link access_show_hidden_entities()}. + * + * @param array $options An options array. {@See elgg_get_annotations()} + * @return bool|null true on success, false on failure, null if no metadata enabled. + * @since 1.8.0 + */ +function elgg_enable_annotations(array $options) { + if (!$options || !is_array($options)) { + return false; } - if ($timeupper) { - $where[] = "a.time_created <= {$timeupper}"; - } + $options['metastring_type'] = 'annotations'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback'); +} - $query = "SELECT a.*, n.string as name, v.string as value - FROM {$CONFIG->dbprefix}annotations a - JOIN {$CONFIG->dbprefix}entities e on a.entity_guid = e.guid - JOIN {$CONFIG->dbprefix}metastrings v on a.value_id=v.id - JOIN {$CONFIG->dbprefix}metastrings n on a.name_id = n.id where "; +/** + * Returns a rendered list of annotations with pagination. + * + * @param array $options Annotation getter and display options. + * {@see elgg_get_annotations()} and {@see elgg_list_entities()}. + * + * @return string The list of entities + * @since 1.8.0 + */ +function elgg_list_annotations($options) { + $defaults = array( + 'limit' => 25, + 'offset' => (int) max(get_input('annoff', 0), 0), + ); - foreach ($where as $w) { - $query .= " $w and "; - } - $query .= get_access_sql_suffix("a"); // Add access controls - $query .= " order by $order_by limit $offset,$limit"; // Add order and limit + $options = array_merge($defaults, $options); - return get_data($query, "row_to_elggannotation"); + return elgg_list_entities($options, 'elgg_get_annotations', 'elgg_view_annotation_list'); } /** - * Returns entities based upon annotations. Accepts the same values as - * elgg_get_entities_from_metadata() but uses the annotations table. + * Entities interfaces + */ + +/** + * Returns entities based upon annotations. Also accepts all options available + * to elgg_get_entities() and elgg_get_entities_from_metadata(). * - * NB: Entity creation time is selected as max_time. To sort based upon + * Entity creation time is selected as maxtime. To sort based upon * this, pass 'order_by' => 'maxtime asc' || 'maxtime desc' * - * time_created in this case will be the time the annotation was created. - * * @see elgg_get_entities * @see elgg_get_entities_from_metadata * @@ -343,9 +320,9 @@ $timeupper = 0, $entity_owner_guid = 0) { * annotation_values => NULL|ARR annotations values * * annotation_name_value_pairs => NULL|ARR (name = 'name', value => 'value', - * 'operand' => '=', 'case_sensitive' => TRUE) entries. + * 'operator' => '=', 'case_sensitive' => TRUE) entries. * Currently if multiple values are sent via an array (value => array('value1', 'value2') - * the pair's operand will be forced to "IN". + * the pair's operator will be forced to "IN". * * annotation_name_value_pairs_operator => NULL|STR The operator to use for combining * (name = value) OPERATOR (name = value); default AND @@ -359,7 +336,7 @@ $timeupper = 0, $entity_owner_guid = 0) { * * annotation_owner_guids => NULL|ARR guids for annotaiton owners * - * @return array + * @return mixed If count, int. If not count, array. false on errors. * @since 1.7.0 */ function elgg_get_entities_from_annotations(array $options = array()) { @@ -372,6 +349,9 @@ function elgg_get_entities_from_annotations(array $options = array()) { 'annotation_case_sensitive' => TRUE, 'order_by_annotation' => array(), + 'annotation_created_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_created_time_upper' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_owner_guids' => ELGG_ENTITIES_ANY_VALUE, 'order_by' => 'maxtime desc', @@ -384,9 +364,10 @@ function elgg_get_entities_from_annotations(array $options = array()) { 'annotation_name_value_pair', 'annotation_owner_guid'); $options = elgg_normalise_plural_options_array($options, $singulars); + $options = elgg_entities_get_metastrings_options('annotation', $options); - if (!$options = elgg_entities_get_metastrings_options('annotation', $options)) { - return FALSE; + if (!$options) { + return false; } // special sorting for annotations @@ -394,727 +375,116 @@ function elgg_get_entities_from_annotations(array $options = array()) { $options['selects'][] = "max(n_table.time_created) as maxtime"; $options['group_by'] = 'n_table.entity_guid'; - return elgg_get_entities($options); -} + $time_wheres = elgg_get_entity_time_where_sql('a', $options['annotation_created_time_upper'], + $options['annotation_created_time_lower']); -/** - * Get entities from annotations - * - * No longer used. - * - * @deprecated 1.7 Use elgg_get_entities_from_annotations() - * - * @param mixed $entity_type Type of entity - * @param mixed $entity_subtype Subtype of entity - * @param string $name Name of annotation - * @param string $value Value of annotation - * @param int $owner_guid Guid of owner of annotation - * @param int $group_guid Guid of group - * @param int $limit Limit - * @param int $offset Offset - * @param string $order_by SQL order by string - * @param bool $count Count or return entities - * @param int $timelower Lower time limit - * @param int $timeupper Upper time limit - * - * @return unknown_type - */ -function get_entities_from_annotations($entity_type = "", $entity_subtype = "", $name = "", -$value = "", $owner_guid = 0, $group_guid = 0, $limit = 10, $offset = 0, $order_by = "asc", -$count = false, $timelower = 0, $timeupper = 0) { - $msg = 'get_entities_from_annotations() is deprecated by elgg_get_entities_from_annotations().'; - elgg_deprecated_notice($msg, 1.7); - - $options = array(); - - $options['annotation_names'] = $name; - - if ($value) { - $options['annotation_values'] = $value; - } - - if ($entity_type) { - $options['types'] = $entity_type; - } - - if ($entity_subtype) { - $options['subtypes'] = $entity_subtype; - } - - if ($owner_guid) { - if (is_array($owner_guid)) { - $options['annotation_owner_guids'] = $owner_guid; - } else { - $options['annotation_owner_guid'] = $owner_guid; - } - } - - if ($group_guid) { - $options['container_guid'] = $group_guid; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } - - if ($order_by) { - $options['order_by'] = "maxtime $order_by"; - } - - if ($count) { - $options['count'] = $count; - } - - return elgg_get_entities_from_annotations($options); -} - -/** - * Lists entities - * - * @see elgg_view_entity_list - * - * @param string $entity_type Type of entity. - * @param string $entity_subtype Subtype of entity. - * @param string $name Name of annotation. - * @param string $value Value of annotation. - * @param int $limit Maximum number of results to return. - * @param int $owner_guid Owner. - * @param int $group_guid Group container. Currently only supported if entity_type is object - * @param boolean $asc Whether to list in ascending or descending order (default: desc) - * @param boolean $fullview Whether to display the entities in full - * @param boolean $viewtypetoggle Can 'gallery' view can be displayed (default: no) - * - * @deprecated 1.7 Use elgg_list_entities_from_annotations() - * @return string Formatted entity list - */ -function list_entities_from_annotations($entity_type = "", $entity_subtype = "", $name = "", -$value = "", $limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, -$viewtypetoggle = false) { - - $msg = 'list_entities_from_annotations is deprecated by elgg_list_entities_from_annotations'; - elgg_deprecated_notice($msg, 1.8); - - $options = array(); - - if ($entity_type) { - $options['types'] = $entity_type; - } - - if ($entity_subtype) { - $options['subtypes'] = $entity_subtype; - } - - if ($name) { - $options['annotation_names'] = $name; - } - - if ($value) { - $options['annotation_values'] = $value; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($owner_guid) { - $options['annotation_owner_guid'] = $owner_guid; - } - - if ($group_guid) { - $options['container_guid'] = $group_guid; - } - - if ($asc) { - $options['order_by'] = 'maxtime desc'; + if ($time_wheres) { + $options['wheres'] = array_merge($options['wheres'], $time_wheres); } - if ($offset = sanitise_int(get_input('offset', null))) { - $options['offset'] = $offset; - } - - $options['full_view'] = $fullview; - $options['view_type_toggle'] = $viewtypetoggle; - $options['pagination'] = $pagination; - - return elgg_list_entities_from_annotations($options); + return elgg_get_entities_from_metadata($options); } /** * Returns a viewable list of entities from annotations. * - * @param array $options - * + * @param array $options Options array + * * @see elgg_get_entities_from_annotations() * @see elgg_list_entities() * - * @return str + * @return string */ function elgg_list_entities_from_annotations($options = array()) { return elgg_list_entities($options, 'elgg_get_entities_from_annotations'); } /** - * Returns a human-readable list of annotations on a particular entity. - * - * @param int $entity_guid The entity GUID - * @param string $name The name of the kind of annotation - * @param int $limit The number of annotations to display at once - * @param true|false $asc Display annotations in ascending order. (Default: true) - * - * @return string HTML (etc) version of the annotation list - */ -function list_annotations($entity_guid, $name = "", $limit = 25, $asc = true) { - if ($asc) { - $asc = "asc"; - } else { - $asc = "desc"; - } - $count = count_annotations($entity_guid, "", "", $name); - $offset = (int) get_input("annoff", 0); - $annotations = get_annotations($entity_guid, "", "", $name, "", "", $limit, $offset, $asc); - - return elgg_view_annotation_list($annotations, $count, $offset, $limit); -} - -/** - * Return the sum of a given integer annotation. - * - * @param int $entity_guid Guid of Entity - * @param string $entity_type Type of Entity - * @param string $entity_subtype Subtype of Entity - * @param string $name Name of annotation - * @param string $value Value of annotation - * @param string $value_type Type of value - * @param int $owner_guid GUID of owner of annotation - * - * @return int - */ -function get_annotations_sum($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", -$value = "", $value_type = "", $owner_guid = 0) { - - return get_annotations_calculate_x("sum", $entity_guid, $entity_type, $entity_subtype, $name, - $value, $value_type, $owner_guid); -} - -/** - * Return the max of a given integer annotation. - * - * @param int $entity_guid Guid of Entity - * @param string $entity_type Type of Entity - * @param string $entity_subtype Subtype of Entity - * @param string $name Name of annotation - * @param string $value Value of annotation - * @param string $value_type Type of value - * @param int $owner_guid GUID of owner of annotation - * - * @return int - */ -function get_annotations_max($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", -$value = "", $value_type = "", $owner_guid = 0) { - - return get_annotations_calculate_x("max", $entity_guid, $entity_type, $entity_subtype, $name, - $value, $value_type, $owner_guid); -} - -/** - * Return the minumum of a given integer annotation. - * - * @param int $entity_guid Guid of Entity - * @param string $entity_type Type of Entity - * @param string $entity_subtype Subtype of Entity - * @param string $name Name of annotation - * @param string $value Value of annotation - * @param string $value_type Type of value - * @param int $owner_guid GUID of owner of annotation - * - * @return int - */ -function get_annotations_min($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", -$value = "", $value_type = "", $owner_guid = 0) { - - return get_annotations_calculate_x("min", $entity_guid, $entity_type, $entity_subtype, $name, - $value, $value_type, $owner_guid); -} - -/** - * Return the average of a given integer annotation. - * - * @param int $entity_guid Guid of Entity - * @param string $entity_type Type of Entity - * @param string $entity_subtype Subtype of Entity - * @param string $name Name of annotation - * @param string $value Value of annotation - * @param string $value_type Type of value - * @param int $owner_guid GUID of owner of annotation - * - * @return int - */ -function get_annotations_avg($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", -$value = "", $value_type = "", $owner_guid = 0) { - - return get_annotations_calculate_x("avg", $entity_guid, $entity_type, $entity_subtype, $name, - $value, $value_type, $owner_guid); -} - -/** - * Count the number of annotations based on search parameters - * - * @param int $entity_guid Guid of Entity - * @param string $entity_type Type of Entity - * @param string $entity_subtype Subtype of Entity - * @param string $name Name of annotation - * @param string $value Value of annotation - * @param string $value_type Type of value - * @param int $owner_guid GUID of owner of annotation - * @param int $timelower Lower time limit - * @param int $timeupper Upper time limit - * - * @return int - */ -function count_annotations($entity_guid = 0, $entity_type = "", $entity_subtype = "", -$name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, -$timeupper = 0) { - return get_annotations_calculate_x("count", $entity_guid, $entity_type, $entity_subtype, - $name, $value, $value_type, $owner_guid, $timelower, $timeupper); -} - -/** - * Perform a mathmatical calculation on integer annotations. - * - * @param string $sum What sort of calculation to perform - * @param int $entity_guid Guid of Entity - * @param string $entity_type Type of Entity - * @param string $entity_subtype Subtype of Entity - * @param string $name Name of annotation - * @param string $value Value of annotation - * @param string $value_type Type of value - * @param int $owner_guid GUID of owner of annotation - * @param int $timelower Lower time limit - * @param int $timeupper Upper time limit - * - * @return int + * Get entities ordered by a mathematical calculation on annotation values + * + * @param array $options An options array: + * 'calculation' => The calculation to use. Must be a valid MySQL function. + * Defaults to sum. Result selected as 'annotation_calculation'. + * Don't confuse this "calculation" option with the + * "annotation_calculation" option to elgg_get_annotations(). + * This "calculation" option is applied to each entity's set of + * annotations and is selected as annotation_calculation for that row. + * See the docs for elgg_get_annotations() for proper use of the + * "annotation_calculation" option. + * 'order_by' => The order for the sorting. Defaults to 'annotation_calculation desc'. + * 'annotation_names' => The names of annotations on the entity. + * 'annotation_values' => The values of annotations on the entity. + * + * 'metadata_names' => The name of metadata on the entity. + * 'metadata_values' => The value of metadata on the entitiy. + * + * @return mixed If count, int. If not count, array. false on errors. */ -function get_annotations_calculate_x($sum = "avg", $entity_guid, $entity_type = "", -$entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0, -$timelower = 0, $timeupper = 0) { - - global $CONFIG; - - $sum = sanitise_string($sum); - $entity_guid = (int)$entity_guid; - $entity_type = sanitise_string($entity_type); - $timeupper = (int)$timeupper; - $timelower = (int)$timelower; - - if ($entity_subtype) { - if (!$entity_subtype = get_subtype_id($entity_type, $entity_subtype)) { - // requesting a non-existing subtype: return false - return FALSE; - } - } - - if ($name != '' AND !$name = get_metastring_id($name)) { - return 0; - } - - if ($value != '' AND !$value = get_metastring_id($value)) { - return 0; - } - $value_type = sanitise_string($value_type); - $owner_guid = (int)$owner_guid; - - // if (empty($name)) return 0; - - $where = array(); - - if ($entity_guid) { - $where[] = "e.guid=$entity_guid"; - } - - if ($entity_type != "") { - $where[] = "e.type='$entity_type'"; - } - - if ($entity_subtype) { - $where[] = "e.subtype=$entity_subtype"; - } - - if ($name != "") { - $where[] = "a.name_id='$name'"; - } - - if ($value != "") { - $where[] = "a.value_id='$value'"; - } - - if ($value_type != "") { - $where[] = "a.value_type='$value_type'"; - } - - if ($owner_guid) { - $where[] = "a.owner_guid='$owner_guid'"; - } - - if ($timelower) { - $where[] = "a.time_created >= {$timelower}"; - } - - if ($timeupper) { - $where[] = "a.time_created <= {$timeupper}"; - } - - if ($sum != "count") { - $where[] = "a.value_type='integer'"; // Limit on integer types - } - - $query = "SELECT $sum(ms.string) as sum - FROM {$CONFIG->dbprefix}annotations a - JOIN {$CONFIG->dbprefix}entities e on a.entity_guid = e.guid - JOIN {$CONFIG->dbprefix}metastrings ms on a.value_id=ms.id WHERE "; - - foreach ($where as $w) { - $query .= " $w and "; - } - - $query .= get_access_sql_suffix("a"); // now add access - $query .= ' and ' . get_access_sql_suffix("e"); // now add access - - $row = get_data_row($query); - if ($row) { - return $row->sum; - } - - return false; -} - -/** - * Get entities ordered by a mathematical calculation - * - * @param string $sum What sort of calculation to perform - * @param string $entity_type Type of Entity - * @param string $entity_subtype Subtype of Entity - * @param string $name Name of annotation - * @param string $mdname Metadata name - * @param string $mdvalue Metadata value - * @param int $owner_guid GUID of owner of annotation - * @param int $limit Limit of results - * @param int $offset Offset of results - * @param string $orderdir Order of results - * @param bool $count Return count or entities - * - * @return mixed - */ -function get_entities_from_annotations_calculate_x($sum = "sum", $entity_type = "", -$entity_subtype = "", $name = "", $mdname = '', $mdvalue = '', $owner_guid = 0, -$limit = 10, $offset = 0, $orderdir = 'desc', $count = false) { - global $CONFIG; - - $sum = sanitise_string($sum); - $entity_type = sanitise_string($entity_type); - - if ($entity_subtype) { - if (!$entity_subtype = get_subtype_id($entity_type, $entity_subtype)) { - // requesting a non-existing subtype: return false - return FALSE; - } - } - - $name = get_metastring_id($name); - $limit = (int) $limit; - $offset = (int) $offset; - $owner_guid = (int) $owner_guid; - if (!empty($mdname) && !empty($mdvalue)) { - $meta_n = get_metastring_id($mdname); - $meta_v = get_metastring_id($mdvalue); - } - - if (empty($name)) { - return 0; - } - - $where = array(); - - if ($entity_type != "") { - $where[] = "e.type='$entity_type'"; - } - - if ($owner_guid > 0) { - $where[] = "e.container_guid = $owner_guid"; - } - - if ($entity_subtype) { - $where[] = "e.subtype=$entity_subtype"; - } - - if ($name != "") { - $where[] = "a.name_id='$name'"; - } - - if (!empty($mdname) && !empty($mdvalue)) { - if ($mdname != "") { - $where[] = "m.name_id='$meta_n'"; - } - - if ($mdvalue != "") { - $where[] = "m.value_id='$meta_v'"; - } - } - - if ($sum != "count") { - // Limit on integer types - $where[] = "a.value_type='integer'"; - } - - if (!$count) { - $query = "SELECT distinct e.*, $sum(ms.string) as sum "; - } else { - $query = "SELECT count(distinct e.guid) as num, $sum(ms.string) as sum "; - } - $query .= " from {$CONFIG->dbprefix}entities e" - . " JOIN {$CONFIG->dbprefix}annotations a on a.entity_guid = e.guid" - . " JOIN {$CONFIG->dbprefix}metastrings ms on a.value_id=ms.id "; - - if (!empty($mdname) && !empty($mdvalue)) { - $query .= " JOIN {$CONFIG->dbprefix}metadata m on m.entity_guid = e.guid "; - } - - $query .= " WHERE "; - foreach ($where as $w) { - $query .= " $w and "; - } - - $query .= get_access_sql_suffix("a"); // now add access - $query .= ' and ' . get_access_sql_suffix("e"); // now add access - if (!$count) { - $query .= ' group by e.guid'; - } - - if (!$count) { - $query .= ' order by sum ' . $orderdir; - $query .= ' limit ' . $offset . ' , ' . $limit; - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($row = get_data_row($query)) { - return $row->num; - } - } - return false; -} - -/** - * Returns entities ordered by the sum of an annotation - * - * @param string $entity_type Type of Entity - * @param string $entity_subtype Subtype of Entity - * @param string $name Name of annotation - * @param string $mdname Metadata name - * @param string $mdvalue Metadata value - * @param int $owner_guid GUID of owner of annotation - * @param int $limit Limit of results - * @param int $offset Offset of results - * @param string $orderdir Order of results - * @param bool $count Return count or entities - * - * @return unknown - */ -function get_entities_from_annotation_count($entity_type = "", $entity_subtype = "", $name = "", -$mdname = '', $mdvalue = '', $owner_guid = 0, $limit = 10, $offset = 0, $orderdir = 'desc', -$count = false) { - - return get_entities_from_annotations_calculate_x('sum', $entity_type, $entity_subtype, - $name, $mdname, $mdvalue, $owner_guid, $limit, $offset, $orderdir, $count); -} - -/** - * Lists entities by the totals of a particular kind of annotation - * - * @param string $entity_type Type of entity. - * @param string $entity_subtype Subtype of entity. - * @param string $name Name of annotation. - * @param int $limit Maximum number of results to return. - * @param int $owner_guid Owner. - * @param int $group_guid Group container. Currently only supported if entity_type is object - * @param boolean $asc Whether to list in ascending or descending order (default: desc) - * @param boolean $fullview Whether to display the entities in full - * @param boolean $viewtypetoggle Can the 'gallery' view can be displayed (default: no) - * @param boolean $pagination Add pagination - * @param string $orderdir Order desc or asc - * - * @return string Formatted entity list - */ -function list_entities_from_annotation_count($entity_type = "", $entity_subtype = "", $name = "", -$limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, -$viewtypetoggle = false, $pagination = true, $orderdir = 'desc') { - if ($asc) { - $asc = "asc"; - } else { - $asc = "desc"; - } - - $offset = (int) get_input("offset", 0); - $count = get_entities_from_annotation_count($entity_type, $entity_subtype, $name, - '', '', $owner_guid, $limit, $offset, $orderdir, true); - - $entities = get_entities_from_annotation_count($entity_type, $entity_subtype, $name, - '', '', $owner_guid, $limit, $offset, $orderdir, false); - - return elgg_view_entity_list($entities, $count, $offset, $limit, - $fullview, $viewtypetoggle, $pagination); -} - -/** - * Lists entities by the totals of a particular kind of annotation AND - * the value of a piece of metadata - * - * @param string $entity_type Type of entity. - * @param string $entity_subtype Subtype of entity. - * @param string $name Name of annotation. - * @param string $mdname Metadata name - * @param string $mdvalue Metadata value - * @param int $limit Maximum number of results to return. - * @param int $owner_guid Owner. - * @param int $group_guid Group container. Currently only supported if entity_type is object - * @param boolean $asc Whether to list in ascending or descending order (default: desc) - * @param boolean $fullview Whether to display the entities in full - * @param boolean $viewtypetoggle Can the 'gallery' view can be displayed (default: no) - * @param boolean $pagination Display pagination - * @param string $orderdir 'desc' or 'asc' - * - * @return string Formatted entity list - */ -function list_entities_from_annotation_count_by_metadata($entity_type = "", $entity_subtype = "", -$name = "", $mdname = '', $mdvalue = '', $limit = 10, $owner_guid = 0, $group_guid = 0, -$asc = false, $fullview = true, $viewtypetoggle = false, $pagination = true, $orderdir = 'desc') { +function elgg_get_entities_from_annotation_calculation($options) { + $db_prefix = elgg_get_config('dbprefix'); + $defaults = array( + 'calculation' => 'sum', + 'order_by' => 'annotation_calculation desc' + ); - if ($asc) { - $asc = "asc"; - } else { - $asc = "desc"; - } + $options = array_merge($defaults, $options); - $offset = (int) get_input("offset", 0); - $count = get_entities_from_annotation_count($entity_type, $entity_subtype, $name, $mdname, - $mdvalue, $owner_guid, $limit, $offset, $orderdir, true); - $entities = get_entities_from_annotation_count($entity_type, $entity_subtype, $name, $mdname, - $mdvalue, $owner_guid, $limit, $offset, $orderdir, false); + $function = sanitize_string(elgg_extract('calculation', $options, 'sum', false)); - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, - $viewtypetoggle, $pagination); -} + // you must cast this as an int or it sorts wrong. + $options['selects'][] = 'e.*'; + $options['selects'][] = "$function(cast(a_msv.string as signed)) as annotation_calculation"; -/** - * Delete a given annotation. - * - * @param int $id The annotation id - * - * @return bool - */ -function delete_annotation($id) { - global $CONFIG; + // need our own join to get the values because the lower level functions don't + // add all the joins if it's a different callback. + $options['joins'][] = "JOIN {$db_prefix}metastrings a_msv ON n_table.value_id = a_msv.id"; - $id = (int)$id; + // don't need access control because it's taken care of by elgg_get_annotations. + $options['group_by'] = 'n_table.entity_guid'; - $access = get_access_sql_suffix(); - $annotation = get_annotation($id); + $options['callback'] = 'entity_row_to_elggstar'; - if (trigger_elgg_event('delete', 'annotation', $annotation)) { - remove_from_river_by_annotation($id); - return delete_data("DELETE from {$CONFIG->dbprefix}annotations where id=$id and $access"); - } + // see #4393 + // @todo remove after the 'count' shortcut is removed from elgg_get_annotations() + $options['__egefac'] = true; - return FALSE; + return elgg_get_annotations($options); } /** - * Clear all the annotations for a given entity, assuming you have access to that metadata. + * List entities from an annotation calculation. * - * @param int $guid The entity guid - * @param string $name The name of the annotation to delete. - * - * @return int Number of annotations deleted or false if an error - */ -function clear_annotations($guid, $name = "") { - global $CONFIG; - - $guid = (int)$guid; - - if (!empty($name)) { - $name = get_metastring_id($name); - if ($name === false) { - // name doesn't exist so 0 rows were deleted - return 0; - } - } - - $entity_guid = (int) $guid; - if ($entity = get_entity($entity_guid)) { - if ($entity->canEdit()) { - $where = array(); - - if ($name != "") { - $where[] = " name_id='$name'"; - } - - $query = "DELETE from {$CONFIG->dbprefix}annotations where entity_guid=$guid "; - foreach ($where as $w) { - $query .= " and $w"; - } - - return delete_data($query); - } - } - - return FALSE; -} - -/** - * Clear all annotations belonging to a given owner_guid + * @see elgg_get_entities_from_annotation_calculation() * - * @param int $owner_guid The owner + * @param array $options An options array. * - * @return int Number of annotations deleted + * @return string */ -function clear_annotations_by_owner($owner_guid) { - global $CONFIG; - - $owner_guid = (int)$owner_guid; - - $query = "SELECT id from {$CONFIG->dbprefix}annotations WHERE owner_guid=$owner_guid"; - - $annotations = get_data($query); - $deleted = 0; - - if (!$annotations) { - return 0; - } - - foreach ($annotations as $id) { - // Is this the best way? - if (delete_annotation($id->id)) { - $deleted++; - } - } +function elgg_list_entities_from_annotation_calculation($options) { + $defaults = array( + 'calculation' => 'sum', + 'order_by' => 'annotation_calculation desc' + ); + $options = array_merge($defaults, $options); - return $deleted; + return elgg_list_entities($options, 'elgg_get_entities_from_annotation_calculation'); } /** - * Handler called by trigger_plugin_hook on the "export" event. + * Export the annotations for the specified entity * * @param string $hook 'export' - * @param string $entity_type 'all' + * @param string $type 'all' * @param mixed $returnvalue Default return value - * @param mixed $params List of params to export + * @param mixed $params Parameters determining what annotations to export * * @elgg_plugin_hook export all * - * @return mixed + * @return array + * @throws InvalidParameterException + * @access private */ -function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $params) { +function export_annotation_plugin_hook($hook, $type, $returnvalue, $params) { // Sanity check values if ((!is_array($params)) && (!isset($params['guid']))) { throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); @@ -1125,9 +495,12 @@ function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $param } $guid = (int)$params['guid']; - $name = $params['name']; + $options = array('guid' => $guid, 'limit' => 0); + if (isset($params['name'])) { + $options['annotation_name'] = $params['name']; + } - $result = get_annotations($guid); + $result = elgg_get_annotations($options); if ($result) { foreach ($result as $r) { @@ -1149,7 +522,7 @@ function export_annotation_plugin_hook($hook, $entity_type, $returnvalue, $param function get_annotation_url($id) { $id = (int)$id; - if ($extender = get_annotation($id)) { + if ($extender = elgg_get_annotation_from_id($id)) { return get_extender_url($extender); } return false; @@ -1162,24 +535,26 @@ function get_annotation_url($id) { * @param string $annotation_type Type of annotation * @param int $owner_guid Defaults to logged in user. * - * @return true | false + * @return bool + * @since 1.8.0 */ function elgg_annotation_exists($entity_guid, $annotation_type, $owner_guid = NULL) { global $CONFIG; - if (!$owner_guid && !($owner_guid = get_loggedin_userid())) { + if (!$owner_guid && !($owner_guid = elgg_get_logged_in_user_guid())) { return FALSE; } - $entity_guid = (int)$entity_guid; - $annotation_type = sanitise_string($annotation_type); + $entity_guid = sanitize_int($entity_guid); + $owner_guid = sanitize_int($owner_guid); + $annotation_type = sanitize_string($annotation_type); - $sql = "select a.id" . - " FROM {$CONFIG->dbprefix}annotations a, {$CONFIG->dbprefix}metastrings m " . - " WHERE a.owner_guid={$owner_guid} AND a.entity_guid={$entity_guid} " . - " AND a.name_id=m.id AND m.string='{$annotation_type}'"; + $sql = "SELECT a.id FROM {$CONFIG->dbprefix}annotations a" . + " JOIN {$CONFIG->dbprefix}metastrings m ON a.name_id = m.id" . + " WHERE a.owner_guid = $owner_guid AND a.entity_guid = $entity_guid" . + " AND m.string = '$annotation_type'"; - if ($check_annotation = get_data_row($sql)) { + if (get_data_row($sql)) { return TRUE; } @@ -1187,16 +562,57 @@ function elgg_annotation_exists($entity_guid, $annotation_type, $owner_guid = NU } /** + * Return the URL for a comment + * + * @param ElggAnnotation $comment The comment object + * @return string + * @access private + */ +function elgg_comment_url_handler(ElggAnnotation $comment) { + $entity = $comment->getEntity(); + if ($entity) { + return $entity->getURL() . '#item-annotation-' . $comment->id; + } + return ""; +} + +/** * Register an annotation url handler. * - * @param string $function_name The function. * @param string $extender_name The name, default 'all'. + * @param string $function_name The function. * * @return string */ -function register_annotation_url_handler($function_name, $extender_name = "all") { - return register_extender_url_handler($function_name, 'annotation', $extender_name); +function elgg_register_annotation_url_handler($extender_name = "all", $function_name) { + return elgg_register_extender_url_handler('annotation', $extender_name, $function_name); +} + +/** + * Register annotation unit tests + * + * @param string $hook + * @param string $type + * @param array $value + * @param array $params + * @return array + * @access private + */ +function annotations_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/annotations.php'; + return $value; +} + +/** + * Initialize the annotation library + * @access private + */ +function elgg_annotations_init() { + elgg_register_annotation_url_handler('generic_comment', 'elgg_comment_url_handler'); + + elgg_register_plugin_hook_handler("export", "all", "export_annotation_plugin_hook", 2); + elgg_register_plugin_hook_handler('unit_test', 'system', 'annotations_test'); } -/** Register the hook */ -register_plugin_hook("export", "all", "export_annotation_plugin_hook", 2); +elgg_register_event_handler('init', 'system', 'elgg_annotations_init'); diff --git a/engine/lib/cache.php b/engine/lib/cache.php index 8fe7d3562..3116c1a9b 100644 --- a/engine/lib/cache.php +++ b/engine/lib/cache.php @@ -7,16 +7,17 @@ * @subpackage Cache */ +/* Filepath Cache */ + /** - * Returns an ElggCache object suitable for caching view - * file load paths to disk under $CONFIG->dataroot. + * Returns an ElggCache object suitable for caching system information * * @todo Can this be done in a cleaner way? * @todo Swap to memcache etc? * - * @return ElggFileCache A cache object suitable for caching file load paths. + * @return ElggFileCache */ -function elgg_get_filepath_cache() { +function elgg_get_system_cache() { global $CONFIG; /** @@ -25,56 +26,55 @@ function elgg_get_filepath_cache() { static $FILE_PATH_CACHE; if (!$FILE_PATH_CACHE) { - $FILE_PATH_CACHE = new ElggFileCache($CONFIG->dataroot); + $FILE_PATH_CACHE = new ElggFileCache($CONFIG->dataroot . 'system_cache/'); } return $FILE_PATH_CACHE; } /** - * Deletes the view file paths cache from disk. + * Reset the system cache by deleting the caches * - * @return bool On success + * @return void */ -function elgg_filepath_cache_reset() { - $cache = elgg_get_filepath_cache(); - return $cache->delete('view_paths'); +function elgg_reset_system_cache() { + $cache = elgg_get_system_cache(); + $cache->clear(); } /** - * Saves $data to the views file paths disk cache as - * 'view_paths'. + * Saves a system cache. * - * @param mixed $data The data - * - * @return bool On success + * @param string $type The type or identifier of the cache + * @param string $data The data to be saved + * @return bool */ -function elgg_filepath_cache_save($data) { +function elgg_save_system_cache($type, $data) { global $CONFIG; - if ($CONFIG->viewpath_cache_enabled) { - $cache = elgg_get_filepath_cache(); - return $cache->save('view_paths', $data); + if ($CONFIG->system_cache_enabled) { + $cache = elgg_get_system_cache(); + return $cache->save($type, $data); } return false; } /** - * Returns the contents of the views file paths cache from disk. + * Retrieve the contents of a system cache. * - * @return mixed Null if simplecache isn't enabled, the contents of the - * views file paths cache if it is. + * @param string $type The type of cache to load + * @return string */ -function elgg_filepath_cache_load() { +function elgg_load_system_cache($type) { global $CONFIG; - if ($CONFIG->viewpath_cache_enabled) { - $cache = elgg_get_filepath_cache(); - $cached_view_paths = $cache->load('view_paths'); + if ($CONFIG->system_cache_enabled) { + $cache = elgg_get_system_cache(); + $cached_data = $cache->load($type); - if ($cached_view_paths) { - return $cached_view_paths; + if ($cached_data) { + return $cached_data; } } @@ -82,33 +82,372 @@ function elgg_filepath_cache_load() { } /** - * Enables the views file paths disk cache. + * Enables the system disk cache. * - * Uses the 'viewpath_cache_enabled' datalist with a boolean value. - * Resets the views paths cache. + * Uses the 'system_cache_enabled' datalist with a boolean value. + * Resets the system cache. * - * @return null + * @return void */ -function elgg_enable_filepath_cache() { +function elgg_enable_system_cache() { global $CONFIG; - datalist_set('viewpath_cache_enabled', 1); - $CONFIG->viewpath_cache_enabled = 1; - elgg_filepath_cache_reset(); + datalist_set('system_cache_enabled', 1); + $CONFIG->system_cache_enabled = 1; + elgg_reset_system_cache(); } /** - * Disables the views file paths disk cache. + * Disables the system disk cache. * - * Uses the 'viewpath_cache_enabled' datalist with a boolean value. - * Resets the views paths cache. + * Uses the 'system_cache_enabled' datalist with a boolean value. + * Resets the system cache. * - * @return null + * @return void + */ +function elgg_disable_system_cache() { + global $CONFIG; + + datalist_set('system_cache_enabled', 0); + $CONFIG->system_cache_enabled = 0; + elgg_reset_system_cache(); +} + +/** @todo deprecate in Elgg 1.9 **/ + +/** + * @access private + */ +function elgg_get_filepath_cache() { + return elgg_get_system_cache(); +} +/** + * @access private + */ +function elgg_filepath_cache_reset() { + elgg_reset_system_cache(); +} +/** + * @access private + */ +function elgg_filepath_cache_save($type, $data) { + return elgg_save_system_cache($type, $data); +} +/** + * @access private + */ +function elgg_filepath_cache_load($type) { + return elgg_load_system_cache($type); +} +/** + * @access private + */ +function elgg_enable_filepath_cache() { + elgg_enable_system_cache(); +} +/** + * @access private */ function elgg_disable_filepath_cache() { + elgg_disable_system_cache(); +} + +/* Simplecache */ + +/** + * Registers a view to simple cache. + * + * Simple cache is a caching mechanism that saves the output of + * views and its extensions into a file. If the view is called + * by the {@link simplecache/view.php} file, the Elgg framework will + * not be loaded and the contents of the view will returned + * from file. + * + * @warning Simple cached views must take no parameters and return + * the same content no matter who is logged in. + * + * @example + * $blog_js = elgg_get_simplecache_url('js', 'blog/save_draft'); + * elgg_register_simplecache_view('js/blog/save_draft'); + * elgg_register_js('elgg.blog', $blog_js); + * elgg_load_js('elgg.blog'); + * + * @param string $viewname View name + * + * @return void + * @link http://docs.elgg.org/Views/Simplecache + * @see elgg_regenerate_simplecache() + * @since 1.8.0 + */ +function elgg_register_simplecache_view($viewname) { global $CONFIG; - datalist_set('viewpath_cache_enabled', 0); - $CONFIG->viewpath_cache_enabled = 0; - elgg_filepath_cache_reset(); + if (!isset($CONFIG->views)) { + $CONFIG->views = new stdClass; + } + + if (!isset($CONFIG->views->simplecache)) { + $CONFIG->views->simplecache = array(); + } + + $CONFIG->views->simplecache[] = $viewname; } + +/** + * Get the URL for the cached file + * + * @warning You must register the view with elgg_register_simplecache_view() + * for caching to work. See elgg_register_simplecache_view() for a full example. + * + * @param string $type The file type: css or js + * @param string $view The view name + * @return string + * @since 1.8.0 + */ +function elgg_get_simplecache_url($type, $view) { + global $CONFIG; + $lastcache = (int)$CONFIG->lastcache; + $viewtype = elgg_get_viewtype(); + elgg_register_simplecache_view("$type/$view");// see #5302 + if (elgg_is_simplecache_enabled()) { + $url = elgg_get_site_url() . "cache/$type/$viewtype/$view.$lastcache.$type"; + } else { + $url = elgg_get_site_url() . "$type/$view.$lastcache.$type"; + $elements = array("view" => $viewtype); + $url = elgg_http_add_url_query_elements($url, $elements); + } + + return $url; +} + +/** + * Regenerates the simple cache. + * + * @warning This does not invalidate the cache, but actively rebuilds it. + * + * @param string $viewtype Optional viewtype to regenerate. Defaults to all valid viewtypes. + * + * @return void + * @see elgg_register_simplecache_view() + * @since 1.8.0 + */ +function elgg_regenerate_simplecache($viewtype = NULL) { + global $CONFIG; + + if (!isset($CONFIG->views->simplecache) || !is_array($CONFIG->views->simplecache)) { + return; + } + + $lastcached = time(); + + // @todo elgg_view() checks if the page set is done (isset($CONFIG->pagesetupdone)) and + // triggers an event if it's not. Calling elgg_view() here breaks submenus + // (at least) because the page setup hook is called before any + // contexts can be correctly set (since this is called before page_handler()). + // To avoid this, lie about $CONFIG->pagehandlerdone to force + // the trigger correctly when the first view is actually being output. + $CONFIG->pagesetupdone = TRUE; + + if (!file_exists($CONFIG->dataroot . 'views_simplecache')) { + mkdir($CONFIG->dataroot . 'views_simplecache'); + } + + if (isset($viewtype)) { + $viewtypes = array($viewtype); + } else { + $viewtypes = $CONFIG->view_types; + } + + $original_viewtype = elgg_get_viewtype(); + + // disable error reporting so we don't cache problems + $old_debug = elgg_get_config('debug'); + elgg_set_config('debug', null); + + foreach ($viewtypes as $viewtype) { + elgg_set_viewtype($viewtype); + foreach ($CONFIG->views->simplecache as $view) { + $viewcontents = elgg_view($view); + $viewname = md5(elgg_get_viewtype() . $view); + if ($handle = fopen($CONFIG->dataroot . 'views_simplecache/' . $viewname, 'w')) { + fwrite($handle, $viewcontents); + fclose($handle); + } + } + + datalist_set("simplecache_lastupdate_$viewtype", $lastcached); + datalist_set("simplecache_lastcached_$viewtype", $lastcached); + } + + elgg_set_config('debug', $old_debug); + elgg_set_viewtype($original_viewtype); + + // needs to be set for links in html head + $CONFIG->lastcache = $lastcached; + + unset($CONFIG->pagesetupdone); +} + +/** + * Is simple cache enabled + * + * @return bool + * @since 1.8.0 + */ +function elgg_is_simplecache_enabled() { + if (elgg_get_config('simplecache_enabled')) { + return true; + } + + return false; +} + +/** + * Enables the simple cache. + * + * @access private + * @see elgg_register_simplecache_view() + * @return void + * @since 1.8.0 + */ +function elgg_enable_simplecache() { + global $CONFIG; + + datalist_set('simplecache_enabled', 1); + $CONFIG->simplecache_enabled = 1; + elgg_regenerate_simplecache(); +} + +/** + * Disables the simple cache. + * + * @warning Simplecache is also purged when disabled. + * + * @access private + * @see elgg_register_simplecache_view() + * @return void + * @since 1.8.0 + */ +function elgg_disable_simplecache() { + global $CONFIG; + if ($CONFIG->simplecache_enabled) { + datalist_set('simplecache_enabled', 0); + $CONFIG->simplecache_enabled = 0; + + // purge simple cache + if ($handle = opendir($CONFIG->dataroot . 'views_simplecache')) { + while (false !== ($file = readdir($handle))) { + if ($file != "." && $file != "..") { + unlink($CONFIG->dataroot . 'views_simplecache/' . $file); + } + } + closedir($handle); + } + } +} + +/** + * Deletes all cached views in the simplecache and sets the lastcache and + * lastupdate time to 0 for every valid viewtype. + * + * @return bool + * @since 1.7.4 + */ +function elgg_invalidate_simplecache() { + global $CONFIG; + + if (!isset($CONFIG->views->simplecache) || !is_array($CONFIG->views->simplecache)) { + return false; + } + + $handle = opendir($CONFIG->dataroot . 'views_simplecache'); + + if (!$handle) { + return false; + } + + // remove files. + $return = true; + while (false !== ($file = readdir($handle))) { + if ($file != "." && $file != "..") { + $return &= unlink($CONFIG->dataroot . 'views_simplecache/' . $file); + } + } + closedir($handle); + + // reset cache times + $viewtypes = $CONFIG->view_types; + + if (!is_array($viewtypes)) { + return false; + } + + foreach ($viewtypes as $viewtype) { + $return &= datalist_set("simplecache_lastupdate_$viewtype", 0); + $return &= datalist_set("simplecache_lastcached_$viewtype", 0); + } + + return $return; +} + +/** + * @see elgg_reset_system_cache() + * @access private + */ +function _elgg_load_cache() { + global $CONFIG; + + $CONFIG->system_cache_loaded = false; + + $CONFIG->views = new stdClass(); + $data = elgg_load_system_cache('view_locations'); + if (!is_string($data)) { + return; + } + $CONFIG->views->locations = unserialize($data); + + $data = elgg_load_system_cache('view_types'); + if (!is_string($data)) { + return; + } + $CONFIG->view_types = unserialize($data); + + $CONFIG->system_cache_loaded = true; +} + +/** + * @access private + */ +function _elgg_cache_init() { + global $CONFIG; + + $viewtype = elgg_get_viewtype(); + + // Regenerate the simple cache if expired. + // Don't do it on upgrade because upgrade does it itself. + // @todo - move into function and perhaps run off init system event + if (!defined('UPGRADING')) { + $lastupdate = datalist_get("simplecache_lastupdate_$viewtype"); + $lastcached = datalist_get("simplecache_lastcached_$viewtype"); + if ($lastupdate == 0 || $lastcached < $lastupdate) { + elgg_regenerate_simplecache($viewtype); + $lastcached = datalist_get("simplecache_lastcached_$viewtype"); + } + $CONFIG->lastcache = $lastcached; + } + + // cache system data if enabled and not loaded + if ($CONFIG->system_cache_enabled && !$CONFIG->system_cache_loaded) { + elgg_save_system_cache('view_locations', serialize($CONFIG->views->locations)); + elgg_save_system_cache('view_types', serialize($CONFIG->view_types)); + } + + if ($CONFIG->system_cache_enabled && !$CONFIG->i18n_loaded_from_cache) { + reload_all_translations(); + foreach ($CONFIG->translations as $lang => $map) { + elgg_save_system_cache("$lang.lang", serialize($map)); + } + } +} + +elgg_register_event_handler('ready', 'system', '_elgg_cache_init'); diff --git a/engine/lib/calendar.php b/engine/lib/calendar.php index f1a18ddb8..e6f95934c 100644 --- a/engine/lib/calendar.php +++ b/engine/lib/calendar.php @@ -16,6 +16,7 @@ * @param int $year Year * * @return int + * @access private */ function get_day_start($day = null, $month = null, $year = null) { return mktime(0, 0, 0, $month, $day, $year); @@ -29,6 +30,7 @@ function get_day_start($day = null, $month = null, $year = null) { * @param int $year Year * * @return int + * @access private */ function get_day_end($day = null, $month = null, $year = null) { return mktime(23, 59, 59, $month, $day, $year); @@ -37,6 +39,8 @@ function get_day_end($day = null, $month = null, $year = null) { /** * Return the notable entities for a given time period. * + * @todo this function also accepts an array(type => subtypes) for 3rd arg. Should we document this? + * * @param int $start_time The start time as a unix timestamp. * @param int $end_time The end time as a unix timestamp. * @param string $type The type of entity (eg "user", "object" etc) @@ -50,6 +54,7 @@ function get_day_end($day = null, $month = null, $year = null) { * @param mixed $container_guid Container or containers to get entities from (default: any). * * @return array|false + * @access private */ function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", $owner_guid = 0, $order_by = "asc", $limit = 10, $offset = 0, $count = false, $site_guid = 0, @@ -197,6 +202,7 @@ $container_guid = null) { * @param bool $count If true, returns count instead of entities. (Default: false) * * @return int|array A list of entities, or a count if $count is set to true + * @access private */ function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, $meta_value = "", $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", @@ -326,6 +332,7 @@ $site_guid = 0, $count = false) { * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any * * @return array|int|false An array of entities, or the number of entities, or false on failure + * @access private */ function get_noteable_entities_from_relationship($start_time, $end_time, $relationship, $relationship_guid, $inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, @@ -435,6 +442,7 @@ $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { * @param mixed $container_guid Container(s) to get entities from (default: any). * * @return array|false + * @access private */ function get_todays_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = null) { @@ -461,6 +469,7 @@ $limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = null * @param bool $count If true, returns count instead of entities. (Default: false) * * @return int|array A list of entities, or a count if $count is set to true + * @access private */ function get_todays_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, @@ -491,6 +500,7 @@ $count = false) { * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any * * @return array|int|false An array of entities, or the number of entities, or false on failure + * @access private */ function get_todays_entities_from_relationship($relationship, $relationship_guid, $inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, @@ -516,13 +526,14 @@ $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { * @param int $owner_guid The GUID of the owning user * @param int $limit The number of entities to return; 10 by default * @param boolean $fullview Whether or not to display the full view (default: true) - * @param boolean $viewtypetoggle Whether or not to allow gallery view + * @param boolean $listtypetoggle Whether or not to allow gallery view * @param boolean $navigation Display pagination? Default: true * * @return string A viewable list of entities + * @access private */ function list_notable_entities($start_time, $end_time, $type= "", $subtype = "", $owner_guid = 0, -$limit = 10, $fullview = true, $viewtypetoggle = false, $navigation = true) { +$limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { $offset = (int) get_input('offset'); $count = get_notable_entities($start_time, $end_time, $type, $subtype, @@ -532,7 +543,7 @@ $limit = 10, $fullview = true, $viewtypetoggle = false, $navigation = true) { $owner_guid, "", $limit, $offset); return elgg_view_entity_list($entities, $count, $offset, $limit, - $fullview, $viewtypetoggle, $navigation); + $fullview, $listtypetoggle, $navigation); } /** @@ -545,17 +556,18 @@ $limit = 10, $fullview = true, $viewtypetoggle = false, $navigation = true) { * @param int $owner_guid The GUID of the owning user * @param int $limit The number of entities to return; 10 by default * @param boolean $fullview Whether or not to display the full view (default: true) - * @param boolean $viewtypetoggle Whether or not to allow gallery view + * @param boolean $listtypetoggle Whether or not to allow gallery view * @param boolean $navigation Display pagination? Default: true * * @return string A viewable list of entities + * @access private */ function list_todays_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, -$fullview = true, $viewtypetoggle = false, $navigation = true) { +$fullview = true, $listtypetoggle = false, $navigation = true) { $day_start = get_day_start(); $day_end = get_day_end(); return list_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $limit, - $fullview, $viewtypetoggle, $navigation); -}
\ No newline at end of file + $fullview, $listtypetoggle, $navigation); +} diff --git a/engine/lib/configuration.php b/engine/lib/configuration.php index 9b6386c05..55e5bbd36 100644 --- a/engine/lib/configuration.php +++ b/engine/lib/configuration.php @@ -3,8 +3,9 @@ * Elgg configuration procedural code. * * Includes functions for manipulating the configuration values stored in the database - * Plugin authors should use the {@link get_config()}, {@link set_config()}, - * and {@unset_config()} functions to access or update config values. + * Plugin authors should use the {@link elgg_get_config()}, {@link elgg_set_config()}, + * {@link elgg_save_config()}, and {@unset_config()} functions to access or update + * config values. * * Elgg's configuration is split among 2 tables and 1 file: * - dbprefix_config @@ -18,16 +19,170 @@ */ /** + * Get the URL for the current (or specified) site + * + * @param int $site_guid The GUID of the site whose URL we want to grab + * @return string + * @since 1.8.0 + */ +function elgg_get_site_url($site_guid = 0) { + if ($site_guid == 0) { + global $CONFIG; + return $CONFIG->wwwroot; + } + + $site = get_entity($site_guid); + + if (!$site instanceof ElggSite) { + return false; + } + /* @var ElggSite $site */ + + return $site->url; +} + +/** + * Get the plugin path for this installation + * + * @return string + * @since 1.8.0 + */ +function elgg_get_plugins_path() { + global $CONFIG; + return $CONFIG->pluginspath; +} + +/** + * Get the data directory path for this installation + * + * @return string + * @since 1.8.0 + */ +function elgg_get_data_path() { + global $CONFIG; + return $CONFIG->dataroot; +} + +/** + * Get the root directory path for this installation + * + * @return string + * @since 1.8.0 + */ +function elgg_get_root_path() { + global $CONFIG; + return $CONFIG->path; +} + +/** + * Get an Elgg configuration value + * + * @param string $name Name of the configuration value + * @param int $site_guid NULL for installation setting, 0 for default site + * + * @return mixed Configuration value or null if it does not exist + * @since 1.8.0 + */ +function elgg_get_config($name, $site_guid = 0) { + global $CONFIG; + + $name = trim($name); + + if (isset($CONFIG->$name)) { + return $CONFIG->$name; + } + + if ($site_guid === null) { + // installation wide setting + $value = datalist_get($name); + } else { + // hit DB only if we're not sure if value exists or not + if (!isset($CONFIG->site_config_loaded)) { + // site specific setting + if ($site_guid == 0) { + $site_guid = (int) $CONFIG->site_id; + } + $value = get_config($name, $site_guid); + } else { + $value = null; + } + } + + // @todo document why we don't cache false + if ($value === false) { + return null; + } + + $CONFIG->$name = $value; + return $value; +} + +/** + * Set an Elgg configuration value + * + * @warning This does not persist the configuration setting. Use elgg_save_config() + * + * @param string $name Name of the configuration value + * @param mixed $value Value + * + * @return void + * @since 1.8.0 + */ +function elgg_set_config($name, $value) { + global $CONFIG; + + $name = trim($name); + + $CONFIG->$name = $value; +} + +/** + * Save a configuration setting + * + * @param string $name Configuration name (cannot be greater than 255 characters) + * @param mixed $value Configuration value. Should be string for installation setting + * @param int $site_guid NULL for installation setting, 0 for default site + * + * @return bool + * @since 1.8.0 + */ +function elgg_save_config($name, $value, $site_guid = 0) { + global $CONFIG; + + $name = trim($name); + + if (strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); + return false; + } + + elgg_set_config($name, $value); + + if ($site_guid === NULL) { + if (is_array($value) || is_object($value)) { + return false; + } + return datalist_set($name, $value); + } else { + if ($site_guid == 0) { + $site_guid = (int) $CONFIG->site_id; + } + return set_config($name, $value, $site_guid); + } +} + +/** * Check that installation has completed and the database is populated. * - * @throws InstallationException + * @throws InstallationException|DatabaseException * @return void + * @access private */ function verify_installation() { global $CONFIG; if (isset($CONFIG->installed)) { - return $CONFIG->installed; + return; } try { @@ -64,13 +219,21 @@ $DATALIST_CACHE = array(); * * @tip Use datalists to store information common to a full installation. * - * @param string $name The name of the datalist element - * - * @return string|false The datalist value or false if it doesn't exist. + * @param string $name The name of the datalist + * @return string|null|false String if value exists, null if doesn't, false on error + * @access private */ function datalist_get($name) { global $CONFIG, $DATALIST_CACHE; + $name = trim($name); + + // cannot store anything longer than 255 characters in db, so catch here + if (elgg_strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); + return false; + } + $name = sanitise_string($name); if (isset($DATALIST_CACHE[$name])) { return $DATALIST_CACHE[$name]; @@ -109,7 +272,7 @@ function datalist_get($name) { } } - return false; + return null; } /** @@ -118,13 +281,20 @@ function datalist_get($name) { * @param string $name The name of the datalist * @param string $value The new value * - * @return true + * @return bool + * @access private */ function datalist_set($name, $value) { global $CONFIG, $DATALIST_CACHE; - $name = sanitise_string($name); - $value = sanitise_string($value); + // cannot store anything longer than 255 characters in db, so catch before we set + if (elgg_strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); + return false; + } + + $sanitised_name = sanitise_string($name); + $sanitised_value = sanitise_string($value); // If memcache is available then invalidate the cached copy static $datalist_memcache; @@ -136,13 +306,16 @@ function datalist_set($name, $value) { $datalist_memcache->delete($name); } - insert_data("INSERT into {$CONFIG->dbprefix}datalists" - . " set name = '{$name}', value = '{$value}'" - . " ON DUPLICATE KEY UPDATE value='{$value}'"); + $success = insert_data("INSERT into {$CONFIG->dbprefix}datalists" + . " set name = '{$sanitised_name}', value = '{$sanitised_value}'" + . " ON DUPLICATE KEY UPDATE value='{$sanitised_value}'"); - $DATALIST_CACHE[$name] = $value; - - return true; + if ($success !== FALSE) { + $DATALIST_CACHE[$name] = $value; + return true; + } else { + return false; + } } /** @@ -160,6 +333,9 @@ function datalist_set($name, $value) { * This will cause the run once function to be run on all installations. To perform * additional upgrades, create new functions for each release. * + * @warning The function name cannot be longer than 255 characters long due to + * the current schema for the datalist table. + * * @internal A datalist entry $functioname is created with the value of time(). * * @param string $functionname The name of the function you want to run. @@ -169,10 +345,14 @@ function datalist_set($name, $value) { * @return bool */ function run_function_once($functionname, $timelastupdatedcheck = 0) { - if ($lastupdated = datalist_get($functionname)) { + $lastupdated = datalist_get($functionname); + if ($lastupdated) { $lastupdated = (int) $lastupdated; - } else { + } elseif ($lastupdated !== false) { $lastupdated = 0; + } else { + // unable to check datalist + return false; } if (is_callable($functionname) && $lastupdated <= $timelastupdatedcheck) { $functionname(); @@ -201,6 +381,10 @@ function run_function_once($functionname, $timelastupdatedcheck = 0) { function unset_config($name, $site_guid = 0) { global $CONFIG; + if (isset($CONFIG->$name)) { + unset($CONFIG->$name); + } + $name = sanitise_string($name); $site_guid = (int) $site_guid; if ($site_guid == 0) { @@ -224,15 +408,24 @@ function unset_config($name, $site_guid = 0) { * @param string $value Its value * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default) * - * @return 0 + * @return bool * @todo The config table doens't have numeric primary keys so insert_data returns 0. * @todo Use "INSERT ... ON DUPLICATE KEY UPDATE" instead of trying to delete then add. * @see unset_config() * @see get_config() + * @access private */ function set_config($name, $value, $site_guid = 0) { global $CONFIG; + $name = trim($name); + + // cannot store anything longer than 255 characters in db, so catch before we set + if (elgg_strlen($name) > 255) { + elgg_log("The name length for configuration variables cannot be greater than 255", "ERROR"); + return false; + } + // Unset existing unset_config($name, $site_guid); @@ -245,7 +438,8 @@ function set_config($name, $value, $site_guid = 0) { $query = "insert into {$CONFIG->dbprefix}config" . " set name = '{$name}', value = '{$value}', site_guid = {$site_guid}"; - return insert_data($query); + $result = insert_data($query); + return $result !== false; } /** @@ -258,30 +452,65 @@ function set_config($name, $value, $site_guid = 0) { * @param string $name The name of the config value * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default) * - * @return mixed|false + * @return mixed|null * @see set_config() * @see unset_config() + * @access private */ function get_config($name, $site_guid = 0) { global $CONFIG; + $name = sanitise_string($name); + $site_guid = (int) $site_guid; + + // check for deprecated values. + // @todo might be a better spot to define this? + $new_name = false; + switch($name) { + case 'viewpath': + $new_name = 'view_path'; + $dep_version = 1.8; + break; + + case 'pluginspath': + $new_name = 'plugins_path'; + $dep_version = 1.8; + break; + + case 'sitename': + $new_name = 'site_name'; + $dep_version = 1.8; + break; + } + + // @todo these haven't really been implemented in Elgg 1.8. Complete in 1.9. + // show dep message + if ($new_name) { + // $msg = "Config value $name has been renamed as $new_name"; + $name = $new_name; + // elgg_deprecated_notice($msg, $dep_version); + } + + // decide from where to return the value if (isset($CONFIG->$name)) { return $CONFIG->$name; } - $name = sanitise_string($name); - $site_guid = (int) $site_guid; + if ($site_guid == 0) { $site_guid = (int) $CONFIG->site_id; } - if ($result = get_data_row("SELECT value FROM {$CONFIG->dbprefix}config - WHERE name = '{$name}' and site_guid = {$site_guid}")) { + + $result = get_data_row("SELECT value FROM {$CONFIG->dbprefix}config + WHERE name = '{$name}' and site_guid = {$site_guid}"); + + if ($result) { $result = $result->value; $result = unserialize($result->value); $CONFIG->$name = $result; return $result; } - return false; + return null; } /** @@ -290,6 +519,7 @@ function get_config($name, $site_guid = 0) { * @param int $site_guid Optionally, the GUID of the site (current site is assumed by default) * * @return bool + * @access private */ function get_all_config($site_guid = 0) { global $CONFIG; @@ -297,10 +527,10 @@ function get_all_config($site_guid = 0) { $site_guid = (int) $site_guid; if ($site_guid == 0) { - $site_guid = (int) $CONFIG->site_id; + $site_guid = (int) $CONFIG->site_guid; } - if ($result = get_data("SELECT * from {$CONFIG->dbprefix}config where site_guid = {$site_guid}")) { + if ($result = get_data("SELECT * FROM {$CONFIG->dbprefix}config WHERE site_guid = $site_guid")) { foreach ($result as $r) { $name = $r->name; $value = $r->value; @@ -313,72 +543,59 @@ function get_all_config($site_guid = 0) { } /** - * Sets defaults for or attempts to autodetect some common config values and - * loads them into $CONFIG. + * Loads configuration related to this site * - * @return void + * This loads from the config database table and the site entity + * @access private */ -function set_default_config() { +function _elgg_load_site_config() { global $CONFIG; - if (empty($CONFIG->path)) { - $CONFIG->path = str_replace("\\", "/", dirname(dirname(dirname(__FILE__)))) . "/"; - } - - if (empty($CONFIG->viewpath)) { - $CONFIG->viewpath = $CONFIG->path . "views/"; - } - - if (empty($CONFIG->pluginspath)) { - $CONFIG->pluginspath = $CONFIG->path . "mod/"; - } - - if (empty($CONFIG->wwwroot)) { - /* - $CONFIG->wwwroot = "http://" . $_SERVER['SERVER_NAME']; - - $request = $_SERVER['REQUEST_URI']; - - if (strripos($request,"/") < (strlen($request) - 1)) { - // addressing a file directly, not a dir - $request = substr($request, 0, strripos($request,"/")+1); - } - - $CONFIG->wwwroot .= $request; - */ - $pathpart = str_replace("//", "/", str_replace($_SERVER['DOCUMENT_ROOT'], "", $CONFIG->path)); - if (substr($pathpart, 0, 1) != "/") { - $pathpart = "/" . $pathpart; - } - $CONFIG->wwwroot = "http://" . $_SERVER['HTTP_HOST'] . $pathpart; - } - - if (empty($CONFIG->url)) { - $CONFIG->url = $CONFIG->wwwroot; + $CONFIG->site_guid = (int) datalist_get('default_site'); + $CONFIG->site_id = $CONFIG->site_guid; + $CONFIG->site = get_entity($CONFIG->site_guid); + if (!$CONFIG->site) { + throw new InstallationException(elgg_echo('InstallationException:SiteNotInstalled')); } - if (empty($CONFIG->sitename)) { - $CONFIG->sitename = "New Elgg site"; - } + $CONFIG->wwwroot = $CONFIG->site->url; + $CONFIG->sitename = $CONFIG->site->name; + $CONFIG->sitedescription = $CONFIG->site->description; + $CONFIG->siteemail = $CONFIG->site->email; + $CONFIG->url = $CONFIG->wwwroot; - if (empty($CONFIG->language)) { - $CONFIG->language = "en"; - } + get_all_config(); + // gives hint to elgg_get_config function how to approach missing values + $CONFIG->site_config_loaded = true; } /** - * Loads values into $CONFIG. + * Loads configuration related to Elgg as an application * - * If Elgg is installed, this function pulls all rows from dbprefix_config - * and cherry picks some values from dbprefix_datalists. This also extracts - * some commonly used values from the default site object. - * - * @elgg_event boot system - * @return true|null + * This loads from the datalists database table + * @access private */ -function configuration_boot() { +function _elgg_load_application_config() { global $CONFIG; + $install_root = str_replace("\\", "/", dirname(dirname(dirname(__FILE__)))); + $defaults = array( + 'path' => "$install_root/", + 'view_path' => "$install_root/views/", + 'plugins_path' => "$install_root/mod/", + 'language' => 'en', + + // compatibility with old names for plugins not using elgg_get_config() + 'viewpath' => "$install_root/views/", + 'pluginspath' => "$install_root/mod/", + ); + + foreach ($defaults as $name => $value) { + if (empty($CONFIG->$name)) { + $CONFIG->$name = $value; + } + } + $path = datalist_get('path'); if (!empty($path)) { $CONFIG->path = $path; @@ -393,22 +610,23 @@ function configuration_boot() { } else { $CONFIG->simplecache_enabled = 1; } - $viewpath_cache_enabled = datalist_get('viewpath_cache_enabled'); - if ($viewpath_cache_enabled !== false) { - $CONFIG->viewpath_cache_enabled = $viewpath_cache_enabled; + $system_cache_enabled = datalist_get('system_cache_enabled'); + if ($system_cache_enabled !== false) { + $CONFIG->system_cache_enabled = $system_cache_enabled; } else { - $CONFIG->viewpath_cache_enabled = 1; - } - if (isset($CONFIG->site) && ($CONFIG->site instanceof ElggSite)) { - $CONFIG->wwwroot = $CONFIG->site->url; - $CONFIG->sitename = $CONFIG->site->name; - $CONFIG->sitedescription = $CONFIG->site->description; - $CONFIG->siteemail = $CONFIG->site->email; + $CONFIG->system_cache_enabled = 1; } - $CONFIG->url = $CONFIG->wwwroot; - // Load default settings from database - get_all_config(); -} + // initialize context here so it is set before the get_input call + $CONFIG->context = array(); + + // needs to be set before system, init for links in html head + $viewtype = get_input('view', 'default'); + $lastcached = datalist_get("simplecache_lastcached_$viewtype"); + $CONFIG->lastcache = $lastcached; -register_elgg_event_handler('boot', 'system', 'configuration_boot', 10);
\ No newline at end of file + $CONFIG->i18n_loaded_from_cache = false; + + // this must be synced with the enum for the entities table + $CONFIG->entity_types = array('group', 'object', 'site', 'user'); +} diff --git a/engine/lib/cron.php b/engine/lib/cron.php index 811a4cdda..4f3d05b93 100644 --- a/engine/lib/cron.php +++ b/engine/lib/cron.php @@ -7,51 +7,57 @@ */ /** - * Initialisation + * Cron initialization * * @return void + * @access private */ function cron_init() { // Register a pagehandler for cron - register_page_handler('cron', 'cron_page_handler'); + elgg_register_page_handler('cron', 'cron_page_handler'); // register a hook for Walled Garden public pages - register_plugin_hook('public_pages', 'walled_garden', 'cron_public_pages'); + elgg_register_plugin_hook_handler('public_pages', 'walled_garden', 'cron_public_pages'); } /** - * Cron handler for redirecting pages. + * Cron handler * * @param array $page Pages * - * @return void + * @return bool + * @throws CronException + * @access private */ function cron_page_handler($page) { - global $CONFIG; + if (!isset($page[0])) { + forward(); + } - if ($page[0]) { - switch (strtolower($page[0])) { - case 'minute' : - case 'fiveminute' : - case 'fifteenmin' : - case 'halfhour' : - case 'hourly' : - case 'daily' : - case 'weekly' : - case 'monthly': - case 'yearly' : - case 'reboot' : - set_input('period', $page[0]); - break; - default : - throw new CronException(sprintf(elgg_echo('CronException:unknownperiod'), $page[0])); - } + $period = strtolower($page[0]); - // Include cron handler - include($CONFIG->path . "engine/handlers/cron_handler.php"); - } else { - forward(); + $allowed_periods = array( + 'minute', 'fiveminute', 'fifteenmin', 'halfhour', 'hourly', + 'daily', 'weekly', 'monthly', 'yearly', 'reboot' + ); + + if (!in_array($period, $allowed_periods)) { + throw new CronException(elgg_echo('CronException:unknownperiod', array($period))); } + + // Get a list of parameters + $params = array(); + $params['time'] = time(); + + // Data to return to + $old_stdout = ""; + ob_start(); + + $old_stdout = elgg_trigger_plugin_hook('cron', $period, $params, $old_stdout); + $std_out = ob_get_clean(); + + echo $std_out . $old_stdout; + return true; } /** @@ -63,21 +69,21 @@ function cron_page_handler($page) { * @param mixed $params Params * * @return array + * @access private */ function cron_public_pages($hook, $type, $return_value, $params) { - $return_value[] = 'pg/cron/minute'; - $return_value[] = 'pg/cron/fiveminute'; - $return_value[] = 'pg/cron/fifteenmin'; - $return_value[] = 'pg/cron/halfhour'; - $return_value[] = 'pg/cron/hourly'; - $return_value[] = 'pg/cron/daily'; - $return_value[] = 'pg/cron/weekly'; - $return_value[] = 'pg/cron/monthly'; - $return_value[] = 'pg/cron/yearly'; - $return_value[] = 'pg/cron/reboot'; + $return_value[] = 'cron/minute'; + $return_value[] = 'cron/fiveminute'; + $return_value[] = 'cron/fifteenmin'; + $return_value[] = 'cron/halfhour'; + $return_value[] = 'cron/hourly'; + $return_value[] = 'cron/daily'; + $return_value[] = 'cron/weekly'; + $return_value[] = 'cron/monthly'; + $return_value[] = 'cron/yearly'; + $return_value[] = 'cron/reboot'; return $return_value; } -// Register a startup event -register_elgg_event_handler('init', 'system', 'cron_init');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'cron_init'); diff --git a/engine/lib/database.php b/engine/lib/database.php index 16efb5874..a7949788d 100644 --- a/engine/lib/database.php +++ b/engine/lib/database.php @@ -12,14 +12,19 @@ /** * Query cache for all queries. * - * Each query and its results are stored in this array as: + * Each query and its results are stored in this cache as: * <code> - * $DB_QUERY_CACHE[$query] => array(result1, result2, ... resultN) + * $DB_QUERY_CACHE[query hash] => array(result1, result2, ... resultN) * </code> + * @see elgg_query_runner() for details on the hash. * - * @global array $DB_QUERY_CACHE + * @warning Elgg used to set this as an empty array to turn off the cache + * + * @global ElggLRUCache|null $DB_QUERY_CACHE + * @access private */ -$DB_QUERY_CACHE = array(); +global $DB_QUERY_CACHE; +$DB_QUERY_CACHE = null; /** * Queries to be executed upon shutdown. @@ -37,7 +42,9 @@ $DB_QUERY_CACHE = array(); * </code> * * @global array $DB_DELAYED_QUERIES + * @access private */ +global $DB_DELAYED_QUERIES; $DB_DELAYED_QUERIES = array(); /** @@ -46,8 +53,10 @@ $DB_DELAYED_QUERIES = array(); * Each database link created with establish_db_link($name) is stored in * $dblink as $dblink[$name] => resource. Use get_db_link($name) to retrieve it. * - * @global array $dblink + * @global resource[] $dblink + * @access private */ +global $dblink; $dblink = array(); /** @@ -56,7 +65,9 @@ $dblink = array(); * Each call to the database increments this counter. * * @global integer $dbcalls + * @access private */ +global $dbcalls; $dbcalls = 0; /** @@ -68,10 +79,12 @@ $dbcalls = 0; * resource. eg "read", "write", or "readwrite". * * @return void + * @throws DatabaseException + * @access private */ function establish_db_link($dblinkname = "readwrite") { // Get configuration, and globalise database link - global $CONFIG, $dblink, $DB_QUERY_CACHE, $dbcalls; + global $CONFIG, $dblink, $DB_QUERY_CACHE; if ($dblinkname != "readwrite" && isset($CONFIG->db[$dblinkname])) { if (is_array($CONFIG->db[$dblinkname])) { @@ -95,13 +108,13 @@ function establish_db_link($dblinkname = "readwrite") { // Connect to database if (!$dblink[$dblinkname] = mysql_connect($dbhost, $dbuser, $dbpass, true)) { - $msg = sprintf(elgg_echo('DatabaseException:WrongCredentials'), - $dbuser, $dbhost, "****"); + $msg = elgg_echo('DatabaseException:WrongCredentials', + array($dbuser, $dbhost, "****")); throw new DatabaseException($msg); } if (!mysql_select_db($dbname, $dblink[$dblinkname])) { - $msg = sprintf(elgg_echo('DatabaseException:NoConnect'), $dbname); + $msg = elgg_echo('DatabaseException:NoConnect', array($dbname)); throw new DatabaseException($msg); } @@ -115,7 +128,8 @@ function establish_db_link($dblinkname = "readwrite") { // Set up cache if global not initialized and query cache not turned off if ((!$DB_QUERY_CACHE) && (!$db_cache_off)) { - $DB_QUERY_CACHE = new ElggStaticVariableCache('db_query_cache'); + // @todo if we keep this cache in 1.9, expose the size as a config parameter + $DB_QUERY_CACHE = new ElggLRUCache(200); } } @@ -126,9 +140,10 @@ function establish_db_link($dblinkname = "readwrite") { * links up separately; otherwise just create the one database link. * * @return void + * @access private */ function setup_db_connections() { - global $CONFIG, $dblink; + global $CONFIG; if (!empty($CONFIG->db->split)) { establish_db_link('read'); @@ -142,6 +157,7 @@ function setup_db_connections() { * Display profiling information about db at NOTICE debug level upon shutdown. * * @return void + * @access private */ function db_profiling_shutdown_hook() { global $dbcalls; @@ -154,15 +170,23 @@ function db_profiling_shutdown_hook() { * Execute any delayed queries upon shutdown. * * @return void + * @access private */ function db_delayedexecution_shutdown_hook() { - global $DB_DELAYED_QUERIES, $CONFIG; + global $DB_DELAYED_QUERIES; foreach ($DB_DELAYED_QUERIES as $query_details) { - // use one of our db functions so it is included in profiling. - $result = execute_query($query_details['q'], $query_details['l']); - try { + $link = $query_details['l']; + + if ($link == 'read' || $link == 'write') { + $link = get_db_link($link); + } elseif (!is_resource($link)) { + elgg_log("Link for delayed query not valid resource or db_link type. Query: {$query_details['q']}", 'WARNING'); + } + + $result = execute_query($query_details['q'], $link); + if ((isset($query_details['h'])) && (is_callable($query_details['h']))) { $query_details['h']($result); } @@ -174,21 +198,6 @@ function db_delayedexecution_shutdown_hook() { } /** - * Registers shutdown functions for database profiling and delayed queries. - * - * @note Database connections are established upon first call to database. - * - * @return true - * @elgg_event_handler boot system - */ -function init_db() { - register_shutdown_function('db_delayedexecution_shutdown_hook'); - register_shutdown_function('db_profiling_shutdown_hook'); - - return true; -} - -/** * Returns (if required, also creates) a database link resource. * * Database link resources are stored in the {@link $dblink} global. These @@ -197,7 +206,8 @@ function init_db() { * * @param string $dblinktype The type of link we want: "read", "write" or "readwrite". * - * @return object Database link + * @return resource Database link + * @access private */ function get_db_link($dblinktype) { global $dblink; @@ -215,10 +225,11 @@ function get_db_link($dblinktype) { /** * Execute an EXPLAIN for $query. * - * @param str $query The query to explain + * @param string $query The query to explain * @param mixed $link The database link resource to user. * * @return mixed An object of the query's result, or FALSE + * @access private */ function explain_query($query, $link) { if ($result = execute_query("explain " . $query, $link)) { @@ -238,20 +249,26 @@ function explain_query($query, $link) { * {@link $dbcalls} is incremented and the query is saved into the {@link $DB_QUERY_CACHE}. * * @param string $query The query - * @param link $dblink The DB link + * @param resource $dblink The DB link * - * @return The result of mysql_query() + * @return resource result of mysql_query() * @throws DatabaseException + * @access private */ function execute_query($query, $dblink) { - global $CONFIG, $dbcalls, $DB_QUERY_CACHE; + global $dbcalls; + + if ($query == NULL) { + throw new DatabaseException(elgg_echo('DatabaseException:InvalidQuery')); + } + + if (!is_resource($dblink)) { + throw new DatabaseException(elgg_echo('DatabaseException:InvalidDBLink')); + } $dbcalls++; $result = mysql_query($query, $dblink); - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE[$query] = -1; // Set initial cache to -1 - } if (mysql_errno($dblink)) { throw new DatabaseException(mysql_error($dblink) . "\n\n QUERY: " . $query); @@ -267,10 +284,11 @@ function execute_query($query, $dblink) { * the raw result from {@link mysql_query()}. * * @param string $query The query to execute - * @param resource $dblink The database link to use + * @param resource|string $dblink The database link to use or the link type (read | write) * @param string $handler A callback function to pass the results array to * * @return true + * @access private */ function execute_delayed_query($query, $dblink, $handler = "") { global $DB_DELAYED_QUERIES; @@ -279,6 +297,10 @@ function execute_delayed_query($query, $dblink, $handler = "") { $DB_DELAYED_QUERIES = array(); } + if (!is_resource($dblink) && $dblink != 'read' && $dblink != 'write') { + return false; + } + // Construct delayed query $delayed_query = array(); $delayed_query['q'] = $query; @@ -299,9 +321,10 @@ function execute_delayed_query($query, $dblink, $handler = "") { * @return true * @uses execute_delayed_query() * @uses get_db_link() + * @access private */ function execute_delayed_write_query($query, $handler = "") { - return execute_delayed_query($query, get_db_link('write'), $handler); + return execute_delayed_query($query, 'write', $handler); } /** @@ -313,9 +336,10 @@ function execute_delayed_write_query($query, $handler = "") { * @return true * @uses execute_delayed_query() * @uses get_db_link() + * @access private */ function execute_delayed_read_query($query, $handler = "") { - return execute_delayed_query($query, get_db_link('read'), $handler); + return execute_delayed_query($query, 'read', $handler); } /** @@ -327,59 +351,15 @@ function execute_delayed_read_query($query, $handler = "") { * argument to $callback. If no callback function is defined, the * entire result set is returned as an array. * - * If no results are matched, FALSE is returned. - * * @param mixed $query The query being passed. * @param string $callback Optionally, the name of a function to call back to on each row * - * @return array|false An array of database result objects or callback function results or false + * @return array An array of database result objects or callback function results. If the query + * returned nothing, an empty array. + * @access private */ function get_data($query, $callback = "") { - global $CONFIG, $DB_QUERY_CACHE; - - // Is cached? - if ($DB_QUERY_CACHE) { - $cached_query = $DB_QUERY_CACHE[$query]; - } - - if ((isset($cached_query)) && ($cached_query)) { - elgg_log("$query results returned from cache"); - - if ($cached_query === -1) { - // Last time this query returned nothing, so return an empty array - return array(); - } - - return $cached_query; - } - - $dblink = get_db_link('read'); - $resultarray = array(); - - if ($result = execute_query("$query", $dblink)) { - while ($row = mysql_fetch_object($result)) { - if (!empty($callback) && is_callable($callback)) { - $row = $callback($row); - } - if ($row) { - $resultarray[] = $row; - } - } - } - - if (empty($resultarray)) { - elgg_log("DB query \"$query\" returned no results."); - // @todo consider changing this to return empty array #1242 - return FALSE; - } - - // Cache result - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE[$query] = $resultarray; - elgg_log("$query results cached"); - } - - return $resultarray; + return elgg_query_runner($query, $callback, false); } /** @@ -393,49 +373,77 @@ function get_data($query, $callback = "") { * @param string $callback A callback function * * @return mixed A single database result object or the result of the callback function. + * @access private */ function get_data_row($query, $callback = "") { - global $CONFIG, $DB_QUERY_CACHE; + return elgg_query_runner($query, $callback, true); +} - // Is cached - if ($DB_QUERY_CACHE) { - $cached_query = $DB_QUERY_CACHE[$query]; - } +/** + * Handles returning data from a query, running it through a callback function, + * and caching the results. This is for R queries (from CRUD). + * + * @access private + * + * @param string $query The query to execute + * @param string $callback An optional callback function to run on each row + * @param bool $single Return only a single result? + * + * @return array An array of database result objects or callback function results. If the query + * returned nothing, an empty array. + * @since 1.8.0 + * @access private + */ +function elgg_query_runner($query, $callback = null, $single = false) { + global $DB_QUERY_CACHE; - if ((isset($cached_query)) && ($cached_query)) { - elgg_log("$query results returned from cache"); + // Since we want to cache results of running the callback, we need to + // need to namespace the query with the callback and single result request. + // https://github.com/elgg/elgg/issues/4049 + $hash = (string)$callback . (int)$single . $query; - if ($cached_query === -1) { - // Last time this query returned nothing, so return false - //@todo fix me this should return array(). - return FALSE; + // Is cached? + if ($DB_QUERY_CACHE) { + if (isset($DB_QUERY_CACHE[$hash])) { + elgg_log("DB query $query results returned from cache (hash: $hash)", 'NOTICE'); + return $DB_QUERY_CACHE[$hash]; } - - return $cached_query; } $dblink = get_db_link('read'); + $return = array(); if ($result = execute_query("$query", $dblink)) { - $row = mysql_fetch_object($result); - - // Cache result (even if query returned no data) - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE[$query] = $row; - elgg_log("$query results cached"); - } - if (!empty($callback) && is_callable($callback)) { + // test for callback once instead of on each iteration. + // @todo check profiling to see if this needs to be broken out into + // explicit cases instead of checking in the iteration. + $is_callable = is_callable($callback); + while ($row = mysql_fetch_object($result)) { + if ($is_callable) { $row = $callback($row); - } + } - if ($row) { - return $row; + if ($single) { + $return = $row; + break; + } else { + $return[] = $row; + } } } - elgg_log("$query returned no results."); - return FALSE; + if (empty($return)) { + elgg_log("DB query $query returned no results.", 'NOTICE'); + } + + // Cache result + if ($DB_QUERY_CACHE) { + $DB_QUERY_CACHE[$hash] = $return; + elgg_log("DB query $query results cached (hash: $hash)", 'NOTICE'); + } + + return $return; } /** @@ -447,18 +455,15 @@ function get_data_row($query, $callback = "") { * * @return int|false The database id of the inserted row if a AUTO_INCREMENT field is * defined, 0 if not, and false on failure. + * @access private */ function insert_data($query) { - global $CONFIG, $DB_QUERY_CACHE; + elgg_log("DB query $query", 'NOTICE'); + $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - } - - elgg_log("Query cache invalidated"); + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return mysql_insert_id($dblink); @@ -468,24 +473,22 @@ function insert_data($query) { } /** - * Update a row in the database. + * Update the database. * * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}. * * @param string $query The query to run. * - * @return Bool + * @return bool + * @access private */ function update_data($query) { - global $CONFIG, $DB_QUERY_CACHE; + + elgg_log("DB query $query", 'NOTICE'); $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - elgg_log("Query cache invalidated"); - } + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return TRUE; @@ -495,24 +498,22 @@ function update_data($query) { } /** - * Remove a row from the database. + * Remove data from the database. * * @note Altering the DB invalidates all queries in {@link $DB_QUERY_CACHE}. * * @param string $query The SQL query to run * * @return int|false The number of affected rows or false on failure + * @access private */ function delete_data($query) { - global $CONFIG, $DB_QUERY_CACHE; + + elgg_log("DB query $query", 'NOTICE'); $dblink = get_db_link('write'); - // Invalidate query cache - if ($DB_QUERY_CACHE) { - $DB_QUERY_CACHE->clear(); - elgg_log("Query cache invalidated"); - } + _elgg_invalidate_query_cache(); if (execute_query("$query", $dblink)) { return mysql_affected_rows($dblink); @@ -521,6 +522,22 @@ function delete_data($query) { return FALSE; } +/** + * Invalidate the query cache + * + * @access private + */ +function _elgg_invalidate_query_cache() { + global $DB_QUERY_CACHE; + if ($DB_QUERY_CACHE instanceof ElggLRUCache) { + $DB_QUERY_CACHE->clear(); + elgg_log("Query cache invalidated", 'NOTICE'); + } elseif ($DB_QUERY_CACHE) { + // In case someone sets the cache to an array and primes it with data + $DB_QUERY_CACHE = array(); + elgg_log("Query cache invalidated", 'NOTICE'); + } +} /** * Return tables matching the database prefix {@link $CONFIG->dbprefix}% in the currently @@ -528,6 +545,7 @@ function delete_data($query) { * * @return array|false List of tables or false on failure * @static array $tables Tables found matching the database prefix + * @access private */ function get_db_tables() { global $CONFIG; @@ -570,6 +588,7 @@ function get_db_tables() { * @param string $table The name of the table to optimise * * @return bool + * @access private */ function optimize_table($table) { $table = sanitise_string($table); @@ -582,6 +601,7 @@ function optimize_table($table) { * @param resource $dblink The DB link * * @return string Database error message + * @access private */ function get_db_error($dblink) { return mysql_error($dblink); @@ -606,6 +626,7 @@ function get_db_error($dblink) { * * @return void * @throws DatabaseException + * @access private */ function run_sql_script($scriptlocation) { if ($script = file_get_contents($scriptlocation)) { @@ -624,7 +645,7 @@ function run_sql_script($scriptlocation) { $statement = str_replace("prefix_", $CONFIG->dbprefix, $statement); if (!empty($statement)) { try { - $result = update_data($statement); + update_data($statement); } catch (DatabaseException $e) { $errors[] = $e->getMessage(); } @@ -640,73 +661,21 @@ function run_sql_script($scriptlocation) { throw new DatabaseException($msg); } } else { - $msg = sprintf(elgg_echo('DatabaseException:ScriptNotFound'), $scriptlocation); + $msg = elgg_echo('DatabaseException:ScriptNotFound', array($scriptlocation)); throw new DatabaseException($msg); } } /** - * Upgrade the database schema in an ordered sequence. - * - * Executes all upgrade files in elgg/engine/schema/upgrades/ in sequential order. - * Upgrade files must be in the standard Elgg release format of YYYYMMDDII.sql - * where II is an incrementor starting from 01. - * - * Files that are < $version will be ignored. - * - * @warning Plugin authors should not call this function directly. - * - * @param int $version The version you are upgrading from in the format YYYYMMDDII. - * @param string $fromdir Optional directory to load upgrades from. default: engine/schema/upgrades/ - * @param bool $quiet If true, suppress all error messages. Only use for the upgrade from <=1.6. + * Format a query string for logging * - * @return bool - * @see upgrade.php - * @see version.php + * @param string $query Query string + * @return string + * @access private */ -function db_upgrade($version, $fromdir = "", $quiet = FALSE) { - global $CONFIG; - - $version = (int) $version; - - if (!$fromdir) { - $fromdir = $CONFIG->path . 'engine/schema/upgrades/'; - } - - if ($handle = opendir($fromdir)) { - $sqlupgrades = array(); - - while ($sqlfile = readdir($handle)) { - if (!is_dir($fromdir . $sqlfile)) { - if (preg_match('/^([0-9]{10})\.(sql)$/', $sqlfile, $matches)) { - $sql_version = (int) $matches[1]; - if ($sql_version > $version) { - $sqlupgrades[] = $sqlfile; - } - } - } - } - - asort($sqlupgrades); - - if (sizeof($sqlupgrades) > 0) { - foreach ($sqlupgrades as $sqlfile) { - - // hide all errors. - if ($quiet) { - try { - run_sql_script($fromdir . $sqlfile); - } catch (DatabaseException $e) { - error_log($e->getmessage()); - } - } else { - run_sql_script($fromdir . $sqlfile); - } - } - } - } - - return TRUE; +function elgg_format_query($query) { + // remove newlines and extra spaces so logs are easier to read + return preg_replace('/\s\s+/', ' ', $query); } /** @@ -754,26 +723,42 @@ function sanitize_string($string) { /** * Sanitises an integer for database use. * - * @param int $int Integer - * - * @return int Sanitised integer + * @param int $int Value to be sanitized + * @param bool $signed Whether negative values should be allowed (true) + * @return int */ -function sanitise_int($int) { +function sanitise_int($int, $signed = true) { + $int = (int) $int; + + if ($signed === false) { + if ($int < 0) { + $int = 0; + } + } + return (int) $int; } /** - * Wrapper function for alternate English spelling - * - * @param int $int Integer + * Sanitizes an integer for database use. + * Wrapper function for alternate English spelling (@see sanitise_int) * - * @return int Sanitised integer + * @param int $int Value to be sanitized + * @param bool $signed Whether negative values should be allowed (true) + * @return int */ -function sanitize_int($int) { - return (int) $int; +function sanitize_int($int, $signed = true) { + return sanitise_int($int, $signed); } /** - * @elgg_register_event boot system init_db + * Registers shutdown functions for database profiling and delayed queries. + * + * @access private */ -register_elgg_event_handler('boot', 'system', 'init_db', 0);
\ No newline at end of file +function init_db() { + register_shutdown_function('db_delayedexecution_shutdown_hook'); + register_shutdown_function('db_profiling_shutdown_hook'); +} + +elgg_register_event_handler('init', 'system', 'init_db'); diff --git a/engine/lib/deprecated-1.7.php b/engine/lib/deprecated-1.7.php new file mode 100644 index 000000000..ee95b5611 --- /dev/null +++ b/engine/lib/deprecated-1.7.php @@ -0,0 +1,1164 @@ +<?php +/** + * Get entities with the specified access collection id. + * + * @deprecated 1.7. Use elgg_get_entities_from_access_id() + * + * @param int $collection_id ID of collection + * @param string $entity_type Type of entities + * @param string $entity_subtype Subtype of entities + * @param int $owner_guid Guid of owner + * @param int $limit Limit of number of entities to return + * @param int $offset Skip this many entities + * @param string $order_by Column to order by + * @param int $site_guid The site guid + * @param bool $count Return a count or entities + * + * @return array + */ +function get_entities_from_access_id($collection_id, $entity_type = "", $entity_subtype = "", + $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { + // log deprecated warning + elgg_deprecated_notice('get_entities_from_access_id() was deprecated by elgg_get_entities()', 1.7); + + if (!$collection_id) { + return FALSE; + } + + // build the options using given parameters + $options = array(); + $options['limit'] = $limit; + $options['offset'] = $offset; + $options['count'] = $count; + + if ($entity_type) { + $options['type'] = sanitise_string($entity_type); + } + + if ($entity_subtype) { + $options['subtype'] = $entity_subtype; + } + + if ($site_guid) { + $options['site_guid'] = $site_guid; + } + + if ($order_by) { + $options['order_by'] = sanitise_string("e.time_created, $order_by"); + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($site_guid) { + $options['site_guid'] = $site_guid; + } + + $options['access_id'] = $collection_id; + + return elgg_get_entities_from_access_id($options); +} + +/** + * @deprecated 1.7 + */ +function get_entities_from_access_collection($collection_id, $entity_type = "", $entity_subtype = "", + $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { + + elgg_deprecated_notice('get_entities_from_access_collection() was deprecated by elgg_get_entities()', 1.7); + + return get_entities_from_access_id($collection_id, $entity_type, $entity_subtype, + $owner_guid, $limit, $offset, $order_by, $site_guid, $count); +} + +/** + * Get entities from annotations + * + * No longer used. + * + * @deprecated 1.7 Use elgg_get_entities_from_annotations() + * + * @param mixed $entity_type Type of entity + * @param mixed $entity_subtype Subtype of entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param int $owner_guid Guid of owner of annotation + * @param int $group_guid Guid of group + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by SQL order by string + * @param bool $count Count or return entities + * @param int $timelower Lower time limit + * @param int $timeupper Upper time limit + * + * @return unknown_type + */ +function get_entities_from_annotations($entity_type = "", $entity_subtype = "", $name = "", +$value = "", $owner_guid = 0, $group_guid = 0, $limit = 10, $offset = 0, $order_by = "asc", +$count = false, $timelower = 0, $timeupper = 0) { + $msg = 'get_entities_from_annotations() is deprecated by elgg_get_entities_from_annotations().'; + elgg_deprecated_notice($msg, 1.7); + + $options = array(); + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + $options['annotation_names'] = $name; + + if ($value) { + $options['annotation_values'] = $value; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['annotation_owner_guids'] = $owner_guid; + } else { + $options['annotation_owner_guid'] = $owner_guid; + } + } + + if ($group_guid) { + $options['container_guid'] = $group_guid; + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by'] = "maxtime $order_by"; + } + + if ($count) { + $options['count'] = $count; + } + + if ($timelower) { + $options['annotation_created_time_lower'] = $timelower; + } + + if ($timeupper) { + $options['annotation_created_time_upper'] = $timeupper; + } + + return elgg_get_entities_from_annotations($options); +} + +/** + * Lists entities + * + * @see elgg_view_entity_list + * + * @param string $entity_type Type of entity. + * @param string $entity_subtype Subtype of entity. + * @param string $name Name of annotation. + * @param string $value Value of annotation. + * @param int $limit Maximum number of results to return. + * @param int $owner_guid Owner. + * @param int $group_guid Group container. Currently only supported if entity_type is object + * @param boolean $asc Whether to list in ascending or descending order (default: desc) + * @param boolean $fullview Whether to display the entities in full + * @param boolean $listtypetoggle Can 'gallery' view can be displayed (default: no) + * + * @deprecated 1.7 Use elgg_list_entities_from_annotations() + * @return string Formatted entity list + */ +function list_entities_from_annotations($entity_type = "", $entity_subtype = "", $name = "", +$value = "", $limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, +$listtypetoggle = false) { + + $msg = 'list_entities_from_annotations is deprecated by elgg_list_entities_from_annotations'; + elgg_deprecated_notice($msg, 1.8); + + $options = array(); + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + if ($name) { + $options['annotation_names'] = $name; + } + + if ($value) { + $options['annotation_values'] = $value; + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($owner_guid) { + $options['annotation_owner_guid'] = $owner_guid; + } + + if ($group_guid) { + $options['container_guid'] = $group_guid; + } + + if ($asc) { + $options['order_by'] = 'maxtime desc'; + } + + if ($offset = sanitise_int(get_input('offset', null))) { + $options['offset'] = $offset; + } + + $options['full_view'] = $fullview; + $options['list_type_toggle'] = $listtypetoggle; + $options['pagination'] = $pagination; + + return elgg_list_entities_from_annotations($options); +} + +/** + * Returns all php files in a directory. + * + * @deprecated 1.7 Use elgg_get_file_list() instead + * + * @param string $directory Directory to look in + * @param array $exceptions Array of extensions (with .!) to ignore + * @param array $list A list files to include in the return + * + * @return array + */ +function get_library_files($directory, $exceptions = array(), $list = array()) { + elgg_deprecated_notice('get_library_files() deprecated by elgg_get_file_list()', 1.7); + return elgg_get_file_list($directory, $exceptions, $list, array('.php')); +} + +/** + * Add action tokens to URL. + * + * @param string $url URL + * + * @return string + * + * @deprecated 1.7 final + */ +function elgg_validate_action_url($url) { + elgg_deprecated_notice('elgg_validate_action_url() deprecated by elgg_add_action_tokens_to_url().', + 1.7); + + return elgg_add_action_tokens_to_url($url); +} + +/** + * Does nothing. + * + * @deprecated 1.7 + * @return 0 + */ +function test_ip() { + elgg_deprecated_notice('test_ip() was removed because of licensing issues.', 1.7); + + return 0; +} + +/** + * Does nothing. + * + * @return bool + * @deprecated 1.7 + */ +function is_ip_in_array() { + elgg_deprecated_notice('is_ip_in_array() was removed because of licensing issues.', 1.7); + + return false; +} + +/** + * Returns entities. + * + * @deprecated 1.7. Use elgg_get_entities(). + * + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Owner GUID + * @param string $order_by Order by clause + * @param int $limit Limit + * @param int $offset Offset + * @param bool $count Return a count or an array of entities + * @param int $site_guid Site GUID + * @param int $container_guid Container GUID + * @param int $timelower Lower time limit + * @param int $timeupper Upper time limit + * + * @return array + */ +function get_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, +$offset = 0, $count = false, $site_guid = 0, $container_guid = null, $timelower = 0, +$timeupper = 0) { + + elgg_deprecated_notice('get_entities() was deprecated by elgg_get_entities().', 1.7); + + // rewrite owner_guid to container_guid to emulate old functionality + if ($owner_guid != "") { + if (is_null($container_guid)) { + $container_guid = $owner_guid; + $owner_guid = NULL; + } + } + + $options = array(); + if ($type) { + if (is_array($type)) { + $options['types'] = $type; + } else { + $options['type'] = $type; + } + } + + if ($subtype) { + if (is_array($subtype)) { + $options['subtypes'] = $subtype; + } else { + $options['subtype'] = $subtype; + } + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($order_by) { + $options['order_by'] = $order_by; + } + + // need to pass 0 for all option + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($count) { + $options['count'] = $count; + } + + if ($site_guid) { + $options['site_guids'] = $site_guid; + } + + if ($container_guid) { + $options['container_guids'] = $container_guid; + } + + if ($timeupper) { + $options['created_time_upper'] = $timeupper; + } + + if ($timelower) { + $options['created_time_lower'] = $timelower; + } + + $r = elgg_get_entities($options); + return $r; +} + +/** + * Delete multiple entities that match a given query. + * This function iterates through and calls delete_entity on + * each one, this is somewhat inefficient but lets + * the 'delete' event be called for each entity. + * + * @deprecated 1.7. This is a dangerous function as it defaults to deleting everything. + * + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * + * @return false + */ +function delete_entities($type = "", $subtype = "", $owner_guid = 0) { + elgg_deprecated_notice('delete_entities() was deprecated because no one should use it.', 1.7); + return false; +} + +/** + * Lists entities. + * + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param bool $fullview Show entity full views + * @param bool $listtypetoggle Show list type toggle + * @param bool $allowedtypes A string of the allowed types + * + * @return string + * @deprecated 1.7. Use elgg_list_registered_entities(). + */ +function list_registered_entities($owner_guid = 0, $limit = 10, $fullview = true, +$listtypetoggle = false, $allowedtypes = true) { + + elgg_deprecated_notice('list_registered_entities() was deprecated by elgg_list_registered_entities().', 1.7); + + $options = array(); + + // don't want to send anything if not being used. + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($allowedtypes) { + $options['allowed_types'] = $allowedtypes; + } + + // need to send because might be BOOL + $options['full_view'] = $fullview; + $options['list_type_toggle'] = $listtypetoggle; + + $options['offset'] = get_input('offset', 0); + + return elgg_list_registered_entities($options); +} + +/** + * Lists entities + * + * @deprecated 1.7. Use elgg_list_entities(). + * + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param bool $fullview Display entity full views? + * @param bool $listtypetoggle Allow switching to gallery mode? + * @param bool $pagination Show pagination? + * + * @return string + */ +function list_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, +$listtypetoggle = false, $pagination = true) { + + elgg_deprecated_notice('list_entities() was deprecated by elgg_list_entities()!', 1.7); + + $options = array(); + + // rewrite owner_guid to container_guid to emulate old functionality + if ($owner_guid) { + $options['container_guids'] = $owner_guid; + } + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($offset = sanitise_int(get_input('offset', null))) { + $options['offset'] = $offset; + } + + $options['full_view'] = $fullview; + $options['list_type_toggle'] = $listtypetoggle; + $options['pagination'] = $pagination; + + return elgg_list_entities($options); +} + +/** + * Searches for a group based on a complete or partial name or description + * + * @param string $criteria The partial or full name or description + * @param int $limit Limit of the search. + * @param int $offset Offset. + * @param string $order_by The order. + * @param boolean $count Whether to return the count of results or just the results. + * + * @return mixed + * @deprecated 1.7 + */ +function search_for_group($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { + elgg_deprecated_notice('search_for_group() was deprecated by new search plugin.', 1.7); + global $CONFIG; + + $criteria = sanitise_string($criteria); + $limit = (int)$limit; + $offset = (int)$offset; + $order_by = sanitise_string($order_by); + + $access = get_access_sql_suffix("e"); + + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + + if ($count) { + $query = "SELECT count(e.guid) as total "; + } else { + $query = "SELECT e.* "; + } + $query .= "from {$CONFIG->dbprefix}entities e" + . " JOIN {$CONFIG->dbprefix}groups_entity g on e.guid=g.guid where "; + + $query .= "(g.name like \"%{$criteria}%\" or g.description like \"%{$criteria}%\")"; + $query .= " and $access"; + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * Returns a formatted list of groups suitable for injecting into search. + * + * @deprecated 1.7 + * + * @param string $hook Hook name + * @param string $user User + * @param mixed $returnvalue Previous hook's return value + * @param string $tag Tag to search on + * + * @return string + */ +function search_list_groups_by_name($hook, $user, $returnvalue, $tag) { + elgg_deprecated_notice('search_list_groups_by_name() was deprecated by new search plugin', 1.7); + // Change this to set the number of groups that display on the search page + $threshold = 4; + + $object = get_input('object'); + + if (!get_input('offset') && (empty($object) || $object == 'group')) { + if ($groups = search_for_group($tag, $threshold)) { + $countgroups = search_for_group($tag, 0, 0, "", true); + + $return = elgg_view('group/search/startblurb', array('count' => $countgroups, 'tag' => $tag)); + foreach ($groups as $group) { + $return .= elgg_view_entity($group); + } + $vars = array('count' => $countgroups, 'threshold' => $threshold, 'tag' => $tag); + $return .= elgg_view('group/search/finishblurb', $vars); + return $return; + } + } +} + +/** + * Displays a list of group objects that have been searched for. + * + * @see elgg_view_entity_list + * + * @param string $tag Search criteria + * @param int $limit The number of entities to display on a page + * + * @return string The list in a form suitable to display + * @deprecated 1.7 + */ +function list_group_search($tag, $limit = 10) { + elgg_deprecated_notice('list_group_search() was deprecated by new search plugin.', 1.7); + $offset = (int) get_input('offset'); + $limit = (int) $limit; + $count = (int) search_for_group($tag, 10, 0, '', true); + $entities = search_for_group($tag, $limit, $offset); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, false); + +} + +/** + * Return a list of entities based on the given search criteria. + * + * @deprecated 1.7 use elgg_get_entities_from_metadata(). + * + * @param mixed $meta_name Metadat name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site GUID. 0 for current, -1 for any. + * @param bool $count Return a count instead of entities + * @param bool $case_sensitive Metadata names case sensitivity + * + * @return int|array A list of entities, or a count if $count is set to true + */ +function get_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", +$entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", +$site_guid = 0, $count = FALSE, $case_sensitive = TRUE) { + + elgg_deprecated_notice('get_entities_from_metadata() was deprecated by elgg_get_entities_from_metadata()!', 1.7); + + $options = array(); + + $options['metadata_names'] = $meta_name; + + if ($meta_value) { + $options['metadata_values'] = $meta_value; + } + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + // need to be able to pass false + $options['metadata_case_sensitive'] = $case_sensitive; + + return elgg_get_entities_from_metadata($options); +} + +/** + * Return entities from metadata + * + * @deprecated 1.7. Use elgg_get_entities_from_metadata(). + * + * @param mixed $meta_array Metadata name + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site GUID. 0 for current, -1 for any. + * @param bool $count Return a count instead of entities + * @param bool $meta_array_operator Operator for metadata values + * + * @return int|array A list of entities, or a count if $count is set to true + */ +function get_entities_from_metadata_multi($meta_array, $entity_type = "", $entity_subtype = "", +$owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, +$count = false, $meta_array_operator = 'and') { + + elgg_deprecated_notice('get_entities_from_metadata_multi() was deprecated by elgg_get_entities_from_metadata()!', 1.7); + + if (!is_array($meta_array) || sizeof($meta_array) == 0) { + return false; + } + + $options = array(); + + $options['metadata_name_value_pairs'] = $meta_array; + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + $options['metadata_name_value_pairs_operator'] = $meta_array_operator; + + return elgg_get_entities_from_metadata($options); +} + +/** + * Returns a menu item for use in the children section of add_menu() + * This is not currently used in the Elgg core. + * + * @param string $menu_name The name of the menu item + * @param string $menu_url Its URL + * + * @return stdClass|false Depending on success + * @deprecated 1.7 + */ +function menu_item($menu_name, $menu_url) { + elgg_deprecated_notice('menu_item() is deprecated by add_submenu_item', 1.7); + return make_register_object($menu_name, $menu_url); +} + +/** + * Searches for an object based on a complete or partial title + * or description using full text searching. + * + * IMPORTANT NOTE: With MySQL's default setup: + * 1) $criteria must be 4 or more characters long + * 2) If $criteria matches greater than 50% of results NO RESULTS ARE RETURNED! + * + * @param string $criteria The partial or full name or username. + * @param int $limit Limit of the search. + * @param int $offset Offset. + * @param string $order_by The order. + * @param boolean $count Whether to return the count of results or just the results. + * + * @return int|false + * @deprecated 1.7 + */ +function search_for_object($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { + elgg_deprecated_notice('search_for_object() was deprecated by new search plugin.', 1.7); + global $CONFIG; + + $criteria = sanitise_string($criteria); + $limit = (int)$limit; + $offset = (int)$offset; + $order_by = sanitise_string($order_by); + $container_guid = (int)$container_guid; + + $access = get_access_sql_suffix("e"); + + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + + if ($count) { + $query = "SELECT count(e.guid) as total "; + } else { + $query = "SELECT e.* "; + } + $query .= "from {$CONFIG->dbprefix}entities e + join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid + where match(o.title,o.description) against ('$criteria') and $access"; + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * Returns a formatted list of objects suitable for injecting into search. + * + * @deprecated 1.7 + * + * @param sting $hook Hook + * @param string $user user + * @param mixed $returnvalue Previous return value + * @param mixed $tag Search term + * + * @return array + */ +function search_list_objects_by_name($hook, $user, $returnvalue, $tag) { + elgg_deprecated_notice('search_list_objects_by_name was deprecated by new search plugin.', 1.7); + + // Change this to set the number of users that display on the search page + $threshold = 4; + + $object = get_input('object'); + + if (!get_input('offset') && (empty($object) || $object == 'user')) { + if ($users = search_for_user($tag, $threshold)) { + $countusers = search_for_user($tag, 0, 0, "", true); + + $return = elgg_view('user/search/startblurb', array('count' => $countusers, 'tag' => $tag)); + foreach ($users as $user) { + $return .= elgg_view_entity($user); + } + $return .= elgg_view('user/search/finishblurb', + array('count' => $countusers, 'threshold' => $threshold, 'tag' => $tag)); + + return $return; + + } + } +} + +/** + * Return entities from relationships + * + * @deprecated 1.7 Use elgg_get_entities_from_relationship() + * + * @param string $relationship The relationship type + * @param int $relationship_guid The GUID of the relationship owner + * @param bool $inverse_relationship Invert relationship? + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Entity owner GUID + * @param string $order_by Order by clause + * @param int $limit Limit + * @param int $offset Offset + * @param bool $count Return a count instead of entities? + * @param int $site_guid Site GUID + * + * @return mixed + */ +function get_entities_from_relationship($relationship, $relationship_guid, +$inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + + elgg_deprecated_notice('get_entities_from_relationship() was deprecated by elgg_get_entities_from_relationship()!', 1.7); + + $options = array(); + + $options['relationship'] = $relationship; + $options['relationship_guid'] = $relationship_guid; + $options['inverse_relationship'] = $inverse_relationship; + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_relationship($options); +} + +/** + * Searches for a site based on a complete or partial name + * or description or url using full text searching. + * + * IMPORTANT NOTE: With MySQL's default setup: + * 1) $criteria must be 4 or more characters long + * 2) If $criteria matches greater than 50% of results NO RESULTS ARE RETURNED! + * + * @param string $criteria The partial or full name or username. + * @param int $limit Limit of the search. + * @param int $offset Offset. + * @param string $order_by The order. + * @param boolean $count Whether to return the count of results or just the results. + * + * @return mixed + * @deprecated 1.7 + */ +function search_for_site($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { + elgg_deprecated_notice('search_for_site() was deprecated by new search plugin.', 1.7); + global $CONFIG; + + $criteria = sanitise_string($criteria); + $limit = (int)$limit; + $offset = (int)$offset; + $order_by = sanitise_string($order_by); + + $access = get_access_sql_suffix("e"); + + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + + if ($count) { + $query = "SELECT count(e.guid) as total "; + } else { + $query = "SELECT e.* "; + } + $query .= "from {$CONFIG->dbprefix}entities e + join {$CONFIG->dbprefix}sites_entity s on e.guid=s.guid + where match(s.name, s.description, s.url) against ('$criteria') and $access"; + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * Searches for a user based on a complete or partial name or username. + * + * @param string $criteria The partial or full name or username. + * @param int $limit Limit of the search. + * @param int $offset Offset. + * @param string $order_by The order. + * @param boolean $count Whether to return the count of results or just the results. + * + * @return mixed + * @deprecated 1.7 + */ +function search_for_user($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { + elgg_deprecated_notice('search_for_user() was deprecated by new search.', 1.7); + global $CONFIG; + + $criteria = sanitise_string($criteria); + $limit = (int)$limit; + $offset = (int)$offset; + $order_by = sanitise_string($order_by); + + $access = get_access_sql_suffix("e"); + + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + + if ($count) { + $query = "SELECT count(e.guid) as total "; + } else { + $query = "SELECT e.* "; + } + $query .= "from {$CONFIG->dbprefix}entities e + join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid where "; + + $query .= "(u.name like \"%{$criteria}%\" or u.username like \"%{$criteria}%\")"; + $query .= " and $access"; + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * Displays a list of user objects that have been searched for. + * + * @see elgg_view_entity_list + * + * @param string $tag Search criteria + * @param int $limit The number of entities to display on a page + * + * @return string The list in a form suitable to display + * + * @deprecated 1.7 + */ +function list_user_search($tag, $limit = 10) { + elgg_deprecated_notice('list_user_search() deprecated by new search', 1.7); + $offset = (int) get_input('offset'); + $limit = (int) $limit; + $count = (int) search_for_user($tag, 10, 0, '', true); + $entities = search_for_user($tag, $limit, $offset); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, false); +} + +/** + * Returns a formatted list of users suitable for injecting into search. + * + * @deprecated 1.7 + * + * @param string $hook Hook name + * @param string $user User? + * @param mixed $returnvalue Previous hook's return value + * @param mixed $tag Tag to search against + * + * @return void + */ +function search_list_users_by_name($hook, $user, $returnvalue, $tag) { + elgg_deprecated_notice('search_list_users_by_name() was deprecated by new search', 1.7); + // Change this to set the number of users that display on the search page + $threshold = 4; + + $object = get_input('object'); + + if (!get_input('offset') && (empty($object) || $object == 'user')) { + if ($users = search_for_user($tag, $threshold)) { + $countusers = search_for_user($tag, 0, 0, "", true); + + $return = elgg_view('user/search/startblurb', array('count' => $countusers, 'tag' => $tag)); + foreach ($users as $user) { + $return .= elgg_view_entity($user); + } + + $vars = array('count' => $countusers, 'threshold' => $threshold, 'tag' => $tag); + $return .= elgg_view('user/search/finishblurb', $vars); + return $return; + + } + } +} + +/** + * Extend a view + * + * @deprecated 1.7. Use elgg_extend_view(). + * + * @param string $view The view to extend. + * @param string $view_name This view is added to $view + * @param int $priority The priority, from 0 to 1000, + * to add at (lowest numbers displayed first) + * @param string $viewtype Not used + * + * @return void + */ +function extend_view($view, $view_name, $priority = 501, $viewtype = '') { + elgg_deprecated_notice('extend_view() was deprecated by elgg_extend_view()!', 1.7); + elgg_extend_view($view, $view_name, $priority, $viewtype); +} + +/** + * Get views in a dir + * + * @deprecated 1.7. Use elgg_get_views(). + * + * @param string $dir Dir + * @param string $base Base view + * + * @return array + */ +function get_views($dir, $base) { + elgg_deprecated_notice('get_views() was deprecated by elgg_get_views()!', 1.7); + elgg_get_views($dir, $base); +} + +/** + * Constructs and returns a register object. + * + * @param string $register_name The name of the register + * @param mixed $register_value The value of the register + * @param array $children_array Optionally, an array of children + * + * @return false|stdClass Depending on success + * @deprecated 1.7 Use {@link add_submenu_item()} + */ +function make_register_object($register_name, $register_value, $children_array = array()) { + elgg_deprecated_notice('make_register_object() is deprecated by add_submenu_item()', 1.7); + if (empty($register_name) || empty($register_value)) { + return false; + } + + $register = new stdClass; + $register->name = $register_name; + $register->value = $register_value; + $register->children = $children_array; + + return $register; +} + +/** + * THIS FUNCTION IS DEPRECATED. + * + * Delete a object's extra data. + * + * @todo - this should be removed - was deprecated in 1.5 or earlier + * + * @param int $guid GUID + * + * @return 1 + * @deprecated 1.7 + */ +function delete_object_entity($guid) { + system_message(elgg_echo('deprecatedfunction', array('delete_user_entity'))); + + return 1; // Always return that we have deleted one row in order to not break existing code. +} + +/** + * THIS FUNCTION IS DEPRECATED. + * + * Delete a user's extra data. + * + * @todo remove + * + * @param int $guid User GUID + * + * @return 1 + * @deprecated 1.7 + */ +function delete_user_entity($guid) { + system_message(elgg_echo('deprecatedfunction', array('delete_user_entity'))); + + return 1; // Always return that we have deleted one row in order to not break existing code. +}
\ No newline at end of file diff --git a/engine/lib/deprecated-1.8.php b/engine/lib/deprecated-1.8.php new file mode 100644 index 000000000..91068d047 --- /dev/null +++ b/engine/lib/deprecated-1.8.php @@ -0,0 +1,4820 @@ +<?php +/** + * *************************************************************************** + * NOTE: If this is ever removed from Elgg, sites lose the ability to upgrade + * from 1.7.x and earlier to the latest version of Elgg without upgrading to + * 1.8 first. + * *************************************************************************** + * + * Upgrade the database schema in an ordered sequence. + * + * Executes all upgrade files in elgg/engine/schema/upgrades/ in sequential order. + * Upgrade files must be in the standard Elgg release format of YYYYMMDDII.sql + * where II is an incrementor starting from 01. + * + * Files that are < $version will be ignored. + * + * @warning Plugin authors should not call this function directly. + * + * @param int $version The version you are upgrading from in the format YYYYMMDDII. + * @param string $fromdir Optional directory to load upgrades from. default: engine/schema/upgrades/ + * @param bool $quiet If true, suppress all error messages. Only use for the upgrade from <=1.6. + * + * @return int The number of upgrades run. + * @see upgrade.php + * @see version.php + * @deprecated 1.8 Use PHP upgrades for sql changes. + */ +function db_upgrade($version, $fromdir = "", $quiet = FALSE) { + global $CONFIG; + + elgg_deprecated_notice('db_upgrade() is deprecated by using PHP upgrades.', 1.8); + + $version = (int) $version; + + if (!$fromdir) { + $fromdir = $CONFIG->path . 'engine/schema/upgrades/'; + } + + $i = 0; + + if ($handle = opendir($fromdir)) { + $sqlupgrades = array(); + + while ($sqlfile = readdir($handle)) { + if (!is_dir($fromdir . $sqlfile)) { + if (preg_match('/^([0-9]{10})\.(sql)$/', $sqlfile, $matches)) { + $sql_version = (int) $matches[1]; + if ($sql_version > $version) { + $sqlupgrades[] = $sqlfile; + } + } + } + } + + asort($sqlupgrades); + + if (sizeof($sqlupgrades) > 0) { + foreach ($sqlupgrades as $sqlfile) { + + // hide all errors. + if ($quiet) { + try { + run_sql_script($fromdir . $sqlfile); + } catch (DatabaseException $e) { + error_log($e->getmessage()); + } + } else { + run_sql_script($fromdir . $sqlfile); + } + $i++; + } + } + } + + return $i; +} + +/** + * Lists entities from an access collection + * + * @deprecated 1.8 Use elgg_list_entities_from_access_id() + * + * @return str + */ +function list_entities_from_access_id($access_id, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true) { + + elgg_deprecated_notice("All list_entities* functions were deprecated in 1.8. Use elgg_list_entities* instead.", 1.8); + + echo elgg_list_entities_from_access_id(array('access_id' => $access_id, + 'type' => $entity_type, 'subtype' => $entity_subtype, 'owner_guids' => $owner_guid, + 'limit' => $limit, 'full_view' => $fullview, 'list_type_toggle' => $listtypetoggle, + 'pagination' => $pagination,)); +} + +/** + * Registers a particular action in memory + * + * @deprecated 1.8 Use {@link elgg_register_action()} instead + * + * @param string $action The name of the action (eg "register", "account/settings/save") + * @param boolean $public Can this action be accessed by people not logged into the system? + * @param string $filename Optionally, the filename where this action is located + * @param boolean $admin_only Whether this action is only available to admin users. + */ +function register_action($action, $public = false, $filename = "", $admin_only = false) { + elgg_deprecated_notice("register_action() was deprecated by elgg_register_action()", 1.8); + + if ($admin_only) { + $access = 'admin'; + } elseif ($public) { + $access = 'public'; + } else { + $access = 'logged_in'; + } + + return elgg_register_action($action, $filename, $access); +} + +/** + * Register an admin page with the admin panel. + * This function extends the view "admin/main" with the provided view. + * This view should provide a description and either a control or a link to. + * + * @deprecated 1.8 Extend admin views manually + * + * Usage: + * - To add a control to the main admin panel then extend admin/main + * - To add a control to a new page create a page which renders a view admin/subpage + * (where subpage is your new page - + * nb. some pages already exist that you can extend), extend the main view to point to it, + * and add controls to your new view. + * + * At the moment this is essentially a wrapper around elgg_extend_view(). + * + * @param string $new_admin_view The view associated with the control you're adding + * @param string $view The view to extend, by default this is 'admin/main'. + * @param int $priority Optional priority to govern the appearance in the list. + * + * @return void + */ +function extend_elgg_admin_page($new_admin_view, $view = 'admin/main', $priority = 500) { + elgg_deprecated_notice('extend_elgg_admin_page() does nothing. Extend admin views manually.', 1.8); +} + +/** + * Get entities ordered by a mathematical calculation + * + * @deprecated 1.8 Use elgg_get_entities_from_annotation_calculation() + * + * @param string $sum What sort of calculation to perform + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $mdname Metadata name + * @param string $mdvalue Metadata value + * @param int $owner_guid GUID of owner of annotation + * @param int $limit Limit of results + * @param int $offset Offset of results + * @param string $orderdir Order of results + * @param bool $count Return count or entities + * + * @return mixed + */ +function get_entities_from_annotations_calculate_x($sum = "sum", $entity_type = "", $entity_subtype = "", $name = "", $mdname = '', $mdvalue = '', $owner_guid = 0, $limit = 10, $offset = 0, $orderdir = 'desc', $count = false) { + + $msg = 'get_entities_from_annotations_calculate_x() is deprecated by elgg_get_entities_from_annotation_calculation().'; + + elgg_deprecated_notice($msg, 1.8); + + $options = array(); + + $options['calculation'] = $sum; + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + $options['annotation_names'] = $name; + + if ($mdname) { + $options['metadata_names'] = $mdname; + } + + if ($mdvalue) { + $options['metadata_values'] = $mdvalue; + } + + // original function rewrote this to container guid. + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['container_guids'] = $owner_guid; + } else { + $options['container_guid'] = $owner_guid; + } + } + + $options['limit'] = $limit; + $options['offset'] = $offset; + + $options['order_by'] = "annotation_calculation $orderdir"; + + $options['count'] = $count; + + return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Returns entities ordered by the sum of an annotation + * + * @warning This is function uses sum instead of count. THIS IS SLOW. See #3366. + * This should be used when you have annotations with different values and you + * want a list of entities ordered by the sum of all of those values. + * If you want a list of entities ordered by the number of annotations on each entity, + * use __get_entities_from_annotations_calculate_x() and pass 'count' as the first param. + * + * @deprecated 1.8 Use elgg_get_entities_from_annotation_calculation() + * + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $mdname Metadata name + * @param string $mdvalue Metadata value + * @param int $owner_guid GUID of owner of annotation + * @param int $limit Limit of results + * @param int $offset Offset of results + * @param string $orderdir Order of results + * @param bool $count Return count or entities + * + * @return unknown + */ +function get_entities_from_annotation_count($entity_type = "", $entity_subtype = "", $name = "", $mdname = '', $mdvalue = '', $owner_guid = 0, $limit = 10, $offset = 0, $orderdir = 'desc', $count = false) { + + $msg = 'get_entities_from_annotation_count() is deprecated by elgg_get_entities_from_annotation_calculation().'; + + elgg_deprecated_notice($msg, 1.8); + + $options = array(); + + $options['calculation'] = 'sum'; + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + $options['annotation_names'] = $name; + + if ($mdname) { + $options['metadata_names'] = $mdname; + } + + if ($mdvalue) { + $options['metadata_values'] = $mdvalue; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + $options['limit'] = $limit; + $options['offset'] = $offset; + + $options['order_by'] = "annotation_calculation $orderdir"; + + $options['count'] = $count; + + return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Lists entities by the totals of a particular kind of annotation + * + * @deprecated 1.8 Use elgg_list_entities_from_annotation_calculation() + * + * @param string $entity_type Type of entity. + * @param string $entity_subtype Subtype of entity. + * @param string $name Name of annotation. + * @param int $limit Maximum number of results to return. + * @param int $owner_guid Owner. + * @param int $group_guid Group container. Currently only supported if entity_type is object + * @param boolean $asc Whether to list in ascending or descending order (default: desc) + * @param boolean $fullview Whether to display the entities in full + * @param boolean $listtypetoggle Can the 'gallery' view can be displayed (default: no) + * @param boolean $pagination Add pagination + * @param string $orderdir Order desc or asc + * + * @return string Formatted entity list + */ +function list_entities_from_annotation_count($entity_type = "", $entity_subtype = "", $name = "", $limit = 10, $owner_guid = 0, $group_guid = 0, $asc = false, $fullview = true, $listtypetoggle = false, $pagination = true, $orderdir = 'desc') { + + $msg = 'list_entities_from_annotation_count() is deprecated by elgg_list_entities_from_annotation_calculation().'; + + elgg_deprecated_notice($msg, 1.8); + + $options = array(); + + $options['calculation'] = 'sum'; + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + $options['annotation_names'] = $name; + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + $options['full_view'] = $fullview; + + $options['list_type_toggle'] = $listtypetoggle; + + $options['pagination'] = $pagination; + + $options['limit'] = $limit; + + $options['order_by'] = "annotation_calculation $orderdir"; + + return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Adds an entry in $CONFIG[$register_name][$subregister_name]. + * + * @deprecated 1.8 Use the new menu system. + * + * This is only used for the site-wide menu. See {@link add_menu()}. + * + * @param string $register_name The name of the top-level register + * @param string $subregister_name The name of the subregister + * @param mixed $subregister_value The value of the subregister + * @param array $children_array Optionally, an array of children + * + * @return true|false Depending on success + */ +function add_to_register($register_name, $subregister_name, $subregister_value, $children_array = array()) { + elgg_deprecated_notice("add_to_register() has been deprecated", 1.8); + global $CONFIG; + + if (empty($register_name) || empty($subregister_name)) { + return false; + } + + if (!isset($CONFIG->registers)) { + $CONFIG->registers = array(); + } + + if (!isset($CONFIG->registers[$register_name])) { + $CONFIG->registers[$register_name] = array(); + } + + $subregister = new stdClass; + $subregister->name = $subregister_name; + $subregister->value = $subregister_value; + + if (is_array($children_array)) { + $subregister->children = $children_array; + } + + $CONFIG->registers[$register_name][$subregister_name] = $subregister; + return true; +} + +/** + * Removes a register entry from $CONFIG[register_name][subregister_name] + * + * @deprecated 1.8 Use the new menu system. + * + * This is used to by {@link remove_menu()} to remove site-wide menu items. + * + * @param string $register_name The name of the top-level register + * @param string $subregister_name The name of the subregister + * + * @return true|false Depending on success + * @since 1.7.0 + */ +function remove_from_register($register_name, $subregister_name) { + elgg_deprecated_notice("remove_from_register() has been deprecated", 1.8); + global $CONFIG; + + if (empty($register_name) || empty($subregister_name)) { + return false; + } + + if (!isset($CONFIG->registers)) { + return false; + } + + if (!isset($CONFIG->registers[$register_name])) { + return false; + } + + if (isset($CONFIG->registers[$register_name][$subregister_name])) { + unset($CONFIG->registers[$register_name][$subregister_name]); + return true; + } + + return false; +} + +/** + * If it exists, returns a particular register as an array + * + * @deprecated 1.8 Use the new menu system + * + * @param string $register_name The name of the register + * + * @return array|false Depending on success + */ +function get_register($register_name) { + elgg_deprecated_notice("get_register() has been deprecated", 1.8); + global $CONFIG; + + if ($register_name == 'menu') { + // backward compatible code for site menu + $menu = $CONFIG->menus['site']; + $builder = new ElggMenuBuilder($menu); + $menu_items = $builder->getMenu('text'); + $menu_items = $menu_items['default']; + + $menu = array(); + foreach ($menu_items as $item) { + $subregister = new stdClass; + $subregister->name = $item->getText(); + $subregister->value = $item->getHref(); + $menu[$subregister->name] = $subregister; + } + return $menu; + } + + if (isset($CONFIG->registers[$register_name])) { + return $CONFIG->registers[$register_name]; + } + + return false; +} + +/** + * Deprecated events core function. Code divided between elgg_register_event_handler() + * and trigger_elgg_event(). + * + * @deprecated 1.8 Use explicit register/trigger event functions + * + * @param string $event The type of event (eg 'init', 'update', 'delete') + * @param string $object_type The type of object (eg 'system', 'blog', 'user') + * @param string $function The name of the function that will handle the event + * @param int $priority Priority to call handler. Lower numbers called first (default 500) + * @param boolean $call Set to true to call the event rather than add to it (default false) + * @param mixed $object Optionally, the object the event is being performed on (eg a user) + * + * @return true|false Depending on success + */ +function events($event = "", $object_type = "", $function = "", $priority = 500, $call = false, $object = null) { + + elgg_deprecated_notice('events() has been deprecated.', 1.8); + + // leaving this here just in case someone was directly calling this internal function + if (!$call) { + return elgg_register_event_handler($event, $object_type, $function, $priority); + } else { + return trigger_elgg_event($event, $object_type, $object); + } +} + +/** + * Alias function for events, that registers a function to a particular kind of event + * + * @deprecated 1.8 Use elgg_register_event_handler() instead + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @return true|false Depending on success + */ +function register_elgg_event_handler($event, $object_type, $callback, $priority = 500) { + elgg_deprecated_notice("register_elgg_event_handler() was deprecated by elgg_register_event_handler()", 1.8); + return elgg_register_event_handler($event, $object_type, $callback, $priority); +} + +/** + * Unregisters a function to a particular kind of event + * + * @deprecated 1.8 Use elgg_unregister_event_handler instead + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @since 1.7.0 + */ +function unregister_elgg_event_handler($event, $object_type, $callback) { + elgg_deprecated_notice('unregister_elgg_event_handler => elgg_unregister_event_handler', 1.8); + elgg_unregister_event_handler($event, $object_type, $callback); +} + +/** + * Alias function for events, that triggers a particular kind of event + * + * @deprecated 1.8 Use elgg_trigger_event() instead + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @return true|false Depending on success + */ +function trigger_elgg_event($event, $object_type, $object = null) { + elgg_deprecated_notice('trigger_elgg_event() was deprecated by elgg_trigger_event()', 1.8); + return elgg_trigger_event($event, $object_type, $object); +} + +/** + * Register a function to a plugin hook for a particular entity type, with a given priority. + * + * @deprecated 1.8 Use elgg_register_plugin_hook_handler() instead + * + * eg if you want the function "export_user" to be called when the hook "export" for "user" entities + * is run, use: + * + * register_plugin_hook("export", "user", "export_user"); + * + * "all" is a valid value for both $hook and $entity_type. "none" is a valid value for $entity_type. + * + * The export_user function would then be defined as: + * + * function export_user($hook, $entity_type, $returnvalue, $params); + * + * Where $returnvalue is the return value returned by the last function returned by the hook, and + * $params is an array containing a set of parameters (or nothing). + * + * @param string $hook The name of the hook + * @param string $entity_type The name of the type of entity (eg "user", "object" etc) + * @param string $function The name of a valid function to be run + * @param string $priority The priority - 0 is first, 1000 last, default is 500 + * @return true|false Depending on success + */ +function register_plugin_hook($hook, $type, $callback, $priority = 500) { + elgg_deprecated_notice("register_plugin_hook() was deprecated by elgg_register_plugin_hook_handler()", 1.8); + return elgg_register_plugin_hook_handler($hook, $type, $callback, $priority); +} + +/** + * Unregister a function to a plugin hook for a particular entity type + * + * @deprecated 1.8 Use elgg_unregister_plugin_hook_handler() instead + * + * @param string $hook The name of the hook + * @param string $entity_type The name of the type of entity (eg "user", "object" etc) + * @param string $function The name of a valid function to be run + * @since 1.7.0 + */ +function unregister_plugin_hook($hook, $entity_type, $callback) { + elgg_deprecated_notice("unregister_plugin_hook() was deprecated by elgg_unregister_plugin_hook_handler()", 1.8); + elgg_unregister_plugin_hook_handler($hook, $entity_type, $callback); +} + +/** + * Triggers a plugin hook, with various parameters as an array. For example, to provide + * a 'foo' hook that concerns an entity of type 'bar', with a parameter called 'param1' + * with value 'value1', that by default returns true, you'd call: + * + * @deprecated 1.8 Use elgg_trigger_plugin_hook() instead + * + * trigger_plugin_hook('foo', 'bar', array('param1' => 'value1'), true); + * + * @see register_plugin_hook + * @param string $hook The name of the hook to trigger + * @param string $entity_type The name of the entity type to trigger it for (or "all", or "none") + * @param array $params Any parameters. It's good practice to name the keys, i.e. by using array('name' => 'value', 'name2' => 'value2') + * @param mixed $returnvalue An initial return value + * @return mixed|null The cumulative return value for the plugin hook functions + */ +function trigger_plugin_hook($hook, $type, $params = null, $returnvalue = null) { + elgg_deprecated_notice("trigger_plugin_hook() was deprecated by elgg_trigger_plugin_hook()", 1.8); + return elgg_trigger_plugin_hook($hook, $type, $params, $returnvalue); +} + +/** + * Checks if code is being called from a certain function. + * + * To use, call this function with the function name (and optional + * file location) that it has to be called from, it will either + * return true or false. + * + * e.g. + * + * function my_secure_function() + * { + * if (!call_gatekeeper("my_call_function")) + * return false; + * + * ... do secure stuff ... + * } + * + * function my_call_function() + * { + * // will work + * my_secure_function(); + * } + * + * function bad_function() + * { + * // Will not work + * my_secure_function(); + * } + * + * @param mixed $function The function that this function must have in its call stack, + * to test against a method pass an array containing a class and + * method name. + * @param string $file Optional file that the function must reside in. + * + * @return bool + * + * @deprecated 1.8 A neat but pointless function + */ +function call_gatekeeper($function, $file = "") { + elgg_deprecated_notice("call_gatekeeper() is neat but pointless", 1.8); + // Sanity check + if (!$function) { + return false; + } + + // Check against call stack to see if this is being called from the correct location + $callstack = debug_backtrace(); + $stack_element = false; + + foreach ($callstack as $call) { + if (is_array($function)) { + if ((strcmp($call['class'], $function[0]) == 0) && (strcmp($call['function'], $function[1]) == 0)) { + $stack_element = $call; + } + } else { + if (strcmp($call['function'], $function) == 0) { + $stack_element = $call; + } + } + } + + if (!$stack_element) { + return false; + } + + // If file then check that this it is being called from this function + if ($file) { + $mirror = null; + + if (is_array($function)) { + $mirror = new ReflectionMethod($function[0], $function[1]); + } else { + $mirror = new ReflectionFunction($function); + } + + if ((!$mirror) || (strcmp($file, $mirror->getFileName()) != 0)) { + return false; + } + } + + return true; +} + +/** + * This function checks to see if it is being called at somepoint by a function defined somewhere + * on a given path (optionally including subdirectories). + * + * This function is similar to call_gatekeeper() but returns true if it is being called + * by a method or function which has been defined on a given path or by a specified file. + * + * @param string $path The full path and filename that this function must have + * in its call stack If a partial path is given and + * $include_subdirs is true, then the function will return + * true if called by any function in or below the specified path. + * @param bool $include_subdirs Are subdirectories of the path ok, or must you specify an + * absolute path and filename. + * @param bool $strict_mode If true then the calling method or function must be directly + * called by something on $path, if false the whole call stack is + * searched. + * + * @return void + * + * @deprecated 1.8 A neat but pointless function + */ +function callpath_gatekeeper($path, $include_subdirs = true, $strict_mode = false) { + elgg_deprecated_notice("callpath_gatekeeper() is neat but pointless", 1.8); + + global $CONFIG; + + $path = sanitise_string($path); + + if ($path) { + $callstack = debug_backtrace(); + + foreach ($callstack as $call) { + $call['file'] = str_replace("\\", "/", $call['file']); + + if ($include_subdirs) { + if (strpos($call['file'], $path) === 0) { + + if ($strict_mode) { + $callstack[1]['file'] = str_replace("\\", "/", $callstack[1]['file']); + if ($callstack[1] === $call) { + return true; + } + } else { + return true; + } + } + } else { + if (strcmp($path, $call['file']) == 0) { + if ($strict_mode) { + if ($callstack[1] === $call) { + return true; + } + } else { + return true; + } + } + } + + } + return false; + } + + if (isset($CONFIG->debug)) { + system_message("Gatekeeper'd function called from {$callstack[1]['file']}:" . "{$callstack[1]['line']}\n\nStack trace:\n\n" . print_r($callstack, true)); + } + + return false; +} + +/** + * Returns SQL where clause for owner and containers. + * + * @deprecated 1.8 Use elgg_get_guid_based_where_sql(); + * + * @param string $table Entity table prefix as defined in SELECT...FROM entities $table + * @param NULL|array $owner_guids Owner GUIDs + * + * @return FALSE|str + * @since 1.7.0 + * @access private + */ +function elgg_get_entity_owner_where_sql($table, $owner_guids) { + elgg_deprecated_notice('elgg_get_entity_owner_where_sql() is deprecated by elgg_get_guid_based_where_sql().', 1.8); + + return elgg_get_guid_based_where_sql("{$table}.owner_guid", $owner_guids); +} + +/** + * Returns SQL where clause for containers. + * + * @deprecated 1.8 Use elgg_get_guid_based_where_sql(); + * + * @param string $table Entity table prefix as defined in + * SELECT...FROM entities $table + * @param NULL|array $container_guids Array of container guids + * + * @return FALSE|string + * @since 1.7.0 + * @access private + */ +function elgg_get_entity_container_where_sql($table, $container_guids) { + elgg_deprecated_notice('elgg_get_entity_container_where_sql() is deprecated by elgg_get_guid_based_where_sql().', 1.8); + + return elgg_get_guid_based_where_sql("{$table}.container_guid", $container_guids); +} + +/** + * Returns SQL where clause for site entities + * + * @deprecated 1.8 Use elgg_get_guid_based_where_sql() + * + * @param string $table Entity table prefix as defined in SELECT...FROM entities $table + * @param NULL|array $site_guids Array of site guids + * + * @return FALSE|string + * @since 1.7.0 + * @access private + */ +function elgg_get_entity_site_where_sql($table, $site_guids) { + elgg_deprecated_notice('elgg_get_entity_site_where_sql() is deprecated by elgg_get_guid_based_where_sql().', 1.8); + + return elgg_get_guid_based_where_sql("{$table}.site_guid", $site_guids); +} + +/** + * Return an array of objects in a given container. + * + * @see get_entities() + * + * @param int $group_guid The container (defaults to current page owner) + * @param string $subtype The subtype + * @param int $owner_guid Owner + * @param int $site_guid The site + * @param string $order_by Order + * @param int $limit Limit on number of elements to return, by default 10. + * @param int $offset Where to start, by default 0. + * @param bool $count Whether to return the entities or a count of them. + * + * @return array|false + * @deprecated 1.8 Use elgg_get_entities() instead + */ +function get_objects_in_group($group_guid, $subtype = "", $owner_guid = 0, $site_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = FALSE) { + elgg_deprecated_notice("get_objects_in_group was deprected in 1.8. Use elgg_get_entities() instead", 1.8); + + global $CONFIG; + + if ($subtype === FALSE || $subtype === null || $subtype === 0) { + return FALSE; + } + + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + $order_by = sanitise_string($order_by); + $limit = (int)$limit; + $offset = (int)$offset; + $site_guid = (int)$site_guid; + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + $container_guid = (int)$group_guid; + if ($container_guid == 0) { + $container_guid = elgg_get_page_owner_guid(); + } + + $where = array(); + + $where[] = "e.type='object'"; + + if (!empty($subtype)) { + if (!$subtype = get_subtype_id('object', $subtype)) { + return FALSE; + } + $where[] = "e.subtype=$subtype"; + } + if ($owner_guid != "") { + if (!is_array($owner_guid)) { + $owner_guid = (int)$owner_guid; + $where[] = "e.container_guid = '$owner_guid'"; + } else if (sizeof($owner_guid) > 0) { + // Cast every element to the owner_guid array to int + $owner_guid = array_map("sanitise_int", $owner_guid); + $owner_guid = implode(",", $owner_guid); + $where[] = "e.container_guid in ({$owner_guid})"; + } + } + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + + if ($container_guid > 0) { + $where[] = "e.container_guid = {$container_guid}"; + } + + if (!$count) { + $query = "SELECT * from {$CONFIG->dbprefix}entities e" . " join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid where "; + } else { + $query = "SELECT count(e.guid) as total from {$CONFIG->dbprefix}entities e" . " join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid where "; + } + foreach ($where as $w) { + $query .= " $w and "; + } + + // Add access controls + $query .= get_access_sql_suffix('e'); + if (!$count) { + $query .= " order by $order_by"; + + // Add order and limit + if ($limit) { + $query .= " limit $offset, $limit"; + } + + $dt = get_data($query, "entity_row_to_elggstar"); + return $dt; + } else { + $total = get_data_row($query); + return $total->total; + } +} + +/** + * Lists entities that belong to a group. + * + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $container_guid The GUID of the containing group + * @param int $limit The number of entities to display per page (default: 10) + * @param bool $fullview Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow gallery view (default: true) + * @param bool $pagination Whether to display pagination (default: true) + * + * @return string List of parsed entities + * + * @see elgg_list_entities() + * @deprecated 1.8 Use elgg_list_entities() instead + */ +function list_entities_groups($subtype = "", $owner_guid = 0, $container_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true) { + elgg_deprecated_notice("list_entities_groups was deprecated in 1.8. Use elgg_list_entities() instead.", 1.8); + $offset = (int)get_input('offset'); + $count = get_objects_in_group($container_guid, $subtype, $owner_guid, 0, "", $limit, $offset, true); + $entities = get_objects_in_group($container_guid, $subtype, $owner_guid, 0, "", $limit, $offset); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, $pagination); +} + +/** + * Get all the entities from metadata from a group. + * + * @param int $group_guid The ID of the group. + * @param mixed $meta_name Metadata name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner guid + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site GUID. 0 for current, -1 for any + * @param bool $count Return count instead of entities + * + * @return array|false + * @deprecated 1.8 Use elgg_get_entities_from_metadata() + */ +function get_entities_from_metadata_groups($group_guid, $meta_name, $meta_value = "", $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { + elgg_deprecated_notice("get_entities_from_metadata_groups was deprecated in 1.8.", 1.8); + global $CONFIG; + + $meta_n = get_metastring_id($meta_name); + $meta_v = get_metastring_id($meta_value); + + $entity_type = sanitise_string($entity_type); + $entity_subtype = get_subtype_id($entity_type, $entity_subtype); + $limit = (int)$limit; + $offset = (int)$offset; + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + $order_by = sanitise_string($order_by); + $site_guid = (int)$site_guid; + if (is_array($owner_guid)) { + foreach ($owner_guid as $key => $guid) { + $owner_guid[$key] = (int)$guid; + } + } else { + $owner_guid = (int)$owner_guid; + } + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + $container_guid = (int)$group_guid; + if ($container_guid == 0) { + $container_guid = elgg_get_page_owner_guid(); + } + + $where = array(); + + if ($entity_type != "") { + $where[] = "e.type='$entity_type'"; + } + if ($entity_subtype) { + $where[] = "e.subtype=$entity_subtype"; + } + if ($meta_name != "") { + $where[] = "m.name_id='$meta_n'"; + } + if ($meta_value != "") { + $where[] = "m.value_id='$meta_v'"; + } + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + if ($container_guid > 0) { + $where[] = "e.container_guid = {$container_guid}"; + } + + if (is_array($owner_guid)) { + $where[] = "e.container_guid in (" . implode(",", $owner_guid) . ")"; + } else if ($owner_guid > 0) { + $where[] = "e.container_guid = {$owner_guid}"; + } + + if (!$count) { + $query = "SELECT distinct e.* "; + } else { + $query = "SELECT count(e.guid) as total "; + } + + $query .= "from {$CONFIG->dbprefix}entities e" . " JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid " . " JOIN {$CONFIG->dbprefix}objects_entity o on e.guid = o.guid where"; + + foreach ($where as $w) { + $query .= " $w and "; + } + + // Add access controls + $query .= get_access_sql_suffix("e"); + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($row = get_data_row($query)) { + return $row->total; + } + } + return false; +} + +/** + * As get_entities_from_metadata_groups() but with multiple entities. + * + * @param int $group_guid The ID of the group. + * @param array $meta_array Array of 'name' => 'value' pairs + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site GUID. 0 for current, -1 for any + * @param bool $count Return count of entities instead of entities + * + * @return int|array List of ElggEntities, or the total number if count is set to false + * @deprecated 1.8 Use elgg_get_entities_from_metadata() + */ +function get_entities_from_metadata_groups_multi($group_guid, $meta_array, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, $count = false) { + elgg_deprecated_notice("get_entities_from_metadata_groups_multi was deprecated in 1.8.", 1.8); + + global $CONFIG; + + if (!is_array($meta_array) || sizeof($meta_array) == 0) { + return false; + } + + $where = array(); + + $mindex = 1; + $join = ""; + foreach ($meta_array as $meta_name => $meta_value) { + $meta_n = get_metastring_id($meta_name); + $meta_v = get_metastring_id($meta_value); + $join .= " JOIN {$CONFIG->dbprefix}metadata m{$mindex} on e.guid = m{$mindex}.entity_guid" . " JOIN {$CONFIG->dbprefix}objects_entity o on e.guid = o.guid "; + + if ($meta_name != "") { + $where[] = "m{$mindex}.name_id='$meta_n'"; + } + + if ($meta_value != "") { + $where[] = "m{$mindex}.value_id='$meta_v'"; + } + + $mindex++; + } + + $entity_type = sanitise_string($entity_type); + $entity_subtype = get_subtype_id($entity_type, $entity_subtype); + $limit = (int)$limit; + $offset = (int)$offset; + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + $order_by = sanitise_string($order_by); + $owner_guid = (int)$owner_guid; + + $site_guid = (int)$site_guid; + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + //$access = get_access_list(); + + if ($entity_type != "") { + $where[] = "e.type = '{$entity_type}'"; + } + + if ($entity_subtype) { + $where[] = "e.subtype = {$entity_subtype}"; + } + + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + + if ($owner_guid > 0) { + $where[] = "e.owner_guid = {$owner_guid}"; + } + + if ($container_guid > 0) { + $where[] = "e.container_guid = {$container_guid}"; + } + + if ($count) { + $query = "SELECT count(e.guid) as total "; + } else { + $query = "SELECT distinct e.* "; + } + + $query .= " from {$CONFIG->dbprefix}entities e {$join} where"; + foreach ($where as $w) { + $query .= " $w and "; + } + $query .= get_access_sql_suffix("e"); // Add access controls + + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * List items within a given geographic area. + * + * @param real $lat Latitude + * @param real $long Longitude + * @param real $radius The radius + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $limit The number of entities to display per page (default: 10) + * @param bool $fullview Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow gallery view + * @param bool $navigation Display pagination? Default: true + * + * @return string A viewable list of entities + * @deprecated 1.8 Use elgg_get_entities_from_location() + */ +function list_entities_in_area($lat, $long, $radius, $type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { + elgg_deprecated_notice('list_entities_in_area() was deprecated. Use elgg_list_entities_from_location()', 1.8); + + $options = array(); + + $options['latitude'] = $lat; + $options['longitude'] = $long; + $options['distance'] = $radius; + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + $options['limit'] = $limit; + + $options['full_view'] = $fullview; + $options['list_type_toggle'] = $listtypetoggle; + $options['pagination'] = $pagination; + + return elgg_list_entities_from_location($options); +} + +/** + * List entities in a given location + * + * @param string $location Location + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $limit The number of entities to display per page (default: 10) + * @param bool $fullview Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow gallery view + * @param bool $navigation Display pagination? Default: true + * + * @return string A viewable list of entities + * @deprecated 1.8 Use elgg_list_entities_from_location() + */ +function list_entities_location($location, $type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { + elgg_deprecated_notice('list_entities_location() was deprecated. Use elgg_list_entities_from_metadata()', 1.8); + + return list_entities_from_metadata('location', $location, $type, $subtype, $owner_guid, $limit, $fullview, $listtypetoggle, $navigation); +} + +/** + * Return entities within a given geographic area. + * + * @param float $lat Latitude + * @param float $long Longitude + * @param float $radius The radius + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param string $order_by The field to order by; by default, time_created desc + * @param int $limit The number of entities to return; 10 by default + * @param int $offset The indexing offset, 0 by default + * @param boolean $count Count entities + * @param int $site_guid Site GUID. 0 for current, -1 for any + * @param int|array $container_guid Container GUID + * + * @return array A list of entities. + * @deprecated 1.8 Use elgg_get_entities_from_location() + */ +function get_entities_in_area($lat, $long, $radius, $type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = NULL) { + elgg_deprecated_notice('get_entities_in_area() was deprecated by elgg_get_entities_from_location()!', 1.8); + + $options = array(); + + $options['latitude'] = $lat; + $options['longitude'] = $long; + $options['distance'] = $radius; + + // set container_guid to owner_guid to emulate old functionality + if ($owner_guid != "") { + if (is_null($container_guid)) { + $container_guid = $owner_guid; + } + } + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($container_guid) { + if (is_array($container_guid)) { + $options['container_guids'] = $container_guid; + } else { + $options['container_guid'] = $container_guid; + } + } + + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_location($options); +} + +/** + * Return a list of entities suitable for display based on the given search criteria. + * + * @see elgg_view_entity_list + * + * @deprecated 1.8 Use elgg_list_entities_from_metadata + * + * @param mixed $meta_name Metadata name to search on + * @param mixed $meta_value The value to match, optionally + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity + * @param int $owner_guid Owner GUID + * @param int $limit Number of entities to display per page + * @param bool $fullview WDisplay the full view (default: true) + * @param bool $listtypetoggle Allow users to toggle to the gallery view. Default: true + * @param bool $pagination Display pagination? Default: true + * @param bool $case_sensitive Case sensitive metadata names? + * + * @return string + * + * @return string A list of entities suitable for display + */ +function list_entities_from_metadata($meta_name, $meta_value = "", $entity_type = ELGG_ENTITIES_ANY_VALUE, $entity_subtype = ELGG_ENTITIES_ANY_VALUE, $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true, $case_sensitive = true) { + + elgg_deprecated_notice('list_entities_from_metadata() was deprecated by elgg_list_entities_from_metadata()!', 1.8); + + $offset = (int)get_input('offset'); + $limit = (int)$limit; + $options = array( + 'metadata_name' => $meta_name, + 'metadata_value' => $meta_value, + 'type' => $entity_type, + 'subtype' => $entity_subtype, + 'limit' => $limit, + 'offset' => $offset, + 'count' => TRUE, + 'metadata_case_sensitive' => $case_sensitive + ); + + // previous function allowed falsy $owner_guid for anything + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + $count = elgg_get_entities_from_metadata($options); + + $options['count'] = FALSE; + $entities = elgg_get_entities_from_metadata($options); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, $pagination); +} + +/** + * Returns a viewable list of entities based on the given search criteria. + * + * @see elgg_view_entity_list + * + * @param array $meta_array Array of 'name' => 'value' pairs + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param bool $fullview WDisplay the full view (default: true) + * @param bool $listtypetoggle Allow users to toggle to the gallery view. Default: true + * @param bool $pagination Display pagination? Default: true + * + * @return string List of ElggEntities suitable for display + * + * @deprecated 1.8 Use elgg_list_entities_from_metadata() instead + */ +function list_entities_from_metadata_multi($meta_array, $entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, $listtypetoggle = true, $pagination = true) { + elgg_deprecated_notice(elgg_echo('deprecated:function', array( + 'list_entities_from_metadata_multi', 'elgg_get_entities_from_metadata')), 1.8); + + $offset = (int)get_input('offset'); + $limit = (int)$limit; + $count = get_entities_from_metadata_multi($meta_array, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, "", $site_guid, true); + $entities = get_entities_from_metadata_multi($meta_array, $entity_type, $entity_subtype, $owner_guid, $limit, $offset, "", $site_guid, false); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, $pagination); +} + +/** + * Deprecated by elgg_register_menu_item(). Set $menu_name to 'page'. + * + * @see elgg_register_menu_item() + * @deprecated 1.8 Use the new menu system + * + * @param string $label The label + * @param string $link The link + * @param string $group The group to store item in + * @param boolean $onclick Add a confirmation when clicked? + * @param boolean $selected Is menu item selected + * + * @return bool + */ +function add_submenu_item($label, $link, $group = 'default', $onclick = false, $selected = NULL) { + elgg_deprecated_notice('add_submenu_item was deprecated by elgg_register_menu_item', 1.8); + + // submenu items were added in the page setup hook usually by checking + // the context. We'll pass in the current context here, which will + // emulate that effect. + // if context == 'main' (default) it probably means they always wanted + // the menu item to show up everywhere. + $context = elgg_get_context(); + + if ($context == 'main') { + $context = 'all'; + } + + $item = array('name' => $label, 'text' => $label, 'href' => $link, 'context' => $context, + 'section' => $group,); + + if ($selected) { + $item['selected'] = true; + } + + if ($onclick) { + $js = "onclick=\"javascript:return confirm('" . elgg_echo('deleteconfirm') . "')\""; + $item['vars'] = array('js' => $js); + } + + return elgg_register_menu_item('page', $item); +} + +/** + * Remove an item from submenu by label + * + * @deprecated 1.8 Use the new menu system + * @see elgg_unregister_menu_item() + * + * @param string $label The item label + * @param string $group The submenu group (default "a") + * @return bool whether the item was removed or not + * @since 1.7.8 + */ +function remove_submenu_item($label, $group = 'a') { + elgg_deprecated_notice('remove_submenu_item was deprecated by elgg_unregister_menu_item', 1.8); + + return elgg_unregister_menu_item('page', $label); +} + +/** + * Use elgg_view_menu(). Set $menu_name to 'owner_block'. + * + * @see elgg_view_menu() + * @deprecated 1.8 Use the new menu system. elgg_view_menu() + * + * @return string + */ +function get_submenu() { + elgg_deprecated_notice("get_submenu() has been deprecated by elgg_view_menu()", 1.8); + return elgg_view_menu('owner_block', array('entity' => $owner, + 'class' => 'elgg-menu-owner-block',)); +} + +/** + * Adds an item to the site-wide menu. + * + * You can obtain the menu array by calling {@link get_register('menu')} + * + * @param string $menu_name The name of the menu item + * @param string $menu_url The URL of the page + * @param array $menu_children Optionally, an array of submenu items (not used) + * @param string $context (not used) + * + * @return true|false Depending on success + * @deprecated 1.8 use elgg_register_menu_item() for the menu 'site' + */ +function add_menu($menu_name, $menu_url, $menu_children = array(), $context = "") { + elgg_deprecated_notice('add_menu() deprecated by elgg_register_menu_item()', 1.8); + + return elgg_register_menu_item('site', array('name' => $menu_name, 'text' => $menu_name, + 'href' => $menu_url,)); +} + +/** + * Removes an item from the menu register + * + * @param string $menu_name The name of the menu item + * + * @return true|false Depending on success + * @deprecated 1.8 Use the new menu system + */ +function remove_menu($menu_name) { + elgg_deprecated_notice("remove_menu() deprecated by elgg_unregister_menu_item()", 1.8); + return elgg_unregister_menu_item('site', $menu_name); +} + +/** + * When given a title, returns a version suitable for inclusion in a URL + * + * @param string $title The title + * + * @return string The optimised title + * @deprecated 1.8 Use elgg_get_friendly_title() + */ +function friendly_title($title) { + elgg_deprecated_notice('friendly_title was deprecated by elgg_get_friendly_title', 1.8); + return elgg_get_friendly_title($title); +} + +/** + * Displays a UNIX timestamp in a friendly way (eg "less than a minute ago") + * + * @param int $time A UNIX epoch timestamp + * + * @return string The friendly time + * @deprecated 1.8 Use elgg_view_friendly_time() + */ +function friendly_time($time) { + elgg_deprecated_notice('friendly_time was deprecated by elgg_view_friendly_time', 1.8); + return elgg_view_friendly_time($time); +} + +/** + * Filters a string into an array of significant words + * + * @deprecated 1.8 Don't use this. + * + * @param string $string A string + * + * @return array + */ +function filter_string($string) { + elgg_deprecated_notice('filter_string() was deprecated!', 1.8); + + // Convert it to lower and trim + $string = strtolower($string); + $string = trim($string); + + // Remove links and email addresses + // match protocol://address/path/file.extension?some=variable&another=asf% + $string = preg_replace("/\s([a-zA-Z]+:\/\/[a-z][a-z0-9\_\.\-]*[a-z]{2,6}" . "[a-zA-Z0-9\/\*\-\?\&\%\=]*)([\s|\.|\,])/iu", " ", $string); + + // match www.something.domain/path/file.extension?some=variable&another=asf% + $string = preg_replace("/\s(www\.[a-z][a-z0-9\_\.\-]*[a-z]{2,6}" . "[a-zA-Z0-9\/\*\-\?\&\%\=]*)([\s|\.|\,])/iu", " ", $string); + + // match name@address + $string = preg_replace("/\s([a-zA-Z][a-zA-Z0-9\_\.\-]*[a-zA-Z]" . "*\@[a-zA-Z][a-zA-Z0-9\_\.\-]*[a-zA-Z]{2,6})([\s|\.|\,])/iu", " ", $string); + + // Sanitise the string; remove unwanted characters + $string = preg_replace('/\W/ui', ' ', $string); + + // Explode it into an array + $terms = explode(' ', $string); + + // Remove any blacklist terms + //$terms = array_filter($terms, 'remove_blacklist'); + + return $terms; +} + +/** + * Returns true if the word in $input is considered significant + * + * @deprecated 1.8 Don't use this. + * + * @param string $input A word + * + * @return true|false + */ +function remove_blacklist($input) { + elgg_deprecated_notice('remove_blacklist() was deprecated!', 1.8); + + global $CONFIG; + + if (!is_array($CONFIG->wordblacklist)) { + return $input; + } + + if (strlen($input) < 3 || in_array($input, $CONFIG->wordblacklist)) { + return false; + } + + return true; +} + +/** + * Gets the guid of the entity that owns the current page. + * + * @deprecated 1.8 Use elgg_get_page_owner_guid() + * + * @return int The current page owner guid (0 if none). + */ +function page_owner() { + elgg_deprecated_notice('page_owner() was deprecated by elgg_get_page_owner_guid().', 1.8); + return elgg_get_page_owner_guid(); +} + +/** + * Gets the owner entity for the current page. + * + * @deprecated 1.8 Use elgg_get_page_owner_entity() + * @return ElggEntity|false The current page owner or false if none. + */ +function page_owner_entity() { + elgg_deprecated_notice('page_owner_entity() was deprecated by elgg_get_page_owner_entity().', 1.8); + return elgg_get_page_owner_entity(); +} + +/** + * Registers a page owner handler function + * + * @param string $functionname The callback function + * + * @deprecated 1.8 Use the 'page_owner', 'system' plugin hook + * @return void + */ +function add_page_owner_handler($functionname) { + elgg_deprecated_notice("add_page_owner_handler() was deprecated by the plugin hook 'page_owner', 'system'.", 1.8); +} + +/** + * Set a page owner entity + * + * @param int $entitytoset The GUID of the entity + * + * @deprecated 1.8 Use elgg_set_page_owner_guid() + * @return void + */ +function set_page_owner($entitytoset = -1) { + elgg_deprecated_notice('set_page_owner() was deprecated by elgg_set_page_owner_guid().', 1.8); + elgg_set_page_owner_guid($entitytoset); +} + +/** + * Sets the functional context of a page + * + * @deprecated 1.8 Use elgg_set_context() + * + * @param string $context The context of the page + * + * @return mixed Either the context string, or false on failure + */ +function set_context($context) { + elgg_deprecated_notice('set_context() was deprecated by elgg_set_context().', 1.8); + elgg_set_context($context); + if (empty($context)) { + return false; + } + return $context; +} + +/** + * Returns the functional context of a page + * + * @deprecated 1.8 Use elgg_get_context() + * + * @return string The context, or 'main' if no context has been provided + */ +function get_context() { + elgg_deprecated_notice('get_context() was deprecated by elgg_get_context().', 1.8); + return elgg_get_context(); + + // @todo - used to set context based on calling script + // $context = get_plugin_name(true) +} + +/** + * Returns a list of plugins to load, in the order that they should be loaded. + * + * @deprecated 1.8 Use elgg_get_plugin_ids_in_dir() or elgg_get_plugins() + * + * @return array List of plugins + */ +function get_plugin_list() { + elgg_deprecated_notice('get_plugin_list() is deprecated by elgg_get_plugin_ids_in_dir() or elgg_get_plugins()', 1.8); + + $plugins = elgg_get_plugins('any'); + + $list = array(); + if ($plugins) { + foreach ($plugins as $i => $plugin) { + // in <=1.7 this returned indexed by multiples of 10. + // uh...sure...why not. + $index = ($i + 1) * 10; + $list[$index] = $plugin->getID(); + } + } + + return $list; +} + +/** + * Regenerates the list of known plugins and saves it to the current site + * + * Important: You should regenerate simplecache and the viewpath cache after executing this function + * otherwise you may experience view display artifacts. Do this with the following code: + * + * elgg_regenerate_simplecache(); + * elgg_reset_system_cache(); + * + * @deprecated 1.8 Use elgg_generate_plugin_entities() and elgg_set_plugin_priorities() + * + * @param array $pluginorder Optionally, a list of existing plugins and their orders + * + * @return array The new list of plugins and their orders + */ +function regenerate_plugin_list($pluginorder = FALSE) { + $msg = 'regenerate_plugin_list() is (sorta) deprecated by elgg_generate_plugin_entities() and' + . ' elgg_set_plugin_priorities().'; + elgg_deprecated_notice($msg, 1.8); + + // they're probably trying to set it? + if ($pluginorder) { + if (elgg_generate_plugin_entities()) { + // sort the plugins by the index numerically since we used + // weird indexes in the old system. + ksort($pluginorder, SORT_NUMERIC); + return elgg_set_plugin_priorities($pluginorder); + } + return false; + } else { + // they're probably trying to regenerate from disk? + return elgg_generate_plugin_entities(); + } +} + +/** + * Get the name of the most recent plugin to be called in the + * call stack (or the plugin that owns the current page, if any). + * + * i.e., if the last plugin was in /mod/foobar/, get_plugin_name would return foo_bar. + * + * @deprecated 1.8 Use elgg_get_calling_plugin_id() + * + * @param boolean $mainfilename If set to true, this will instead determine the + * context from the main script filename called by + * the browser. Default = false. + * + * @return string|false Plugin name, or false if no plugin name was called + */ +function get_plugin_name($mainfilename = false) { + elgg_deprecated_notice('get_plugin_name() is deprecated by elgg_get_calling_plugin_id()', 1.8); + + return elgg_get_calling_plugin_id($mainfilename); +} + +/** + * Load and parse a plugin manifest from a plugin XML file. + * + * @example plugins/manifest.xml Example 1.8-style manifest file. + * + * @deprecated 1.8 Use ElggPlugin->getManifest() + * + * @param string $plugin Plugin name. + * @return array of values + */ +function load_plugin_manifest($plugin) { + elgg_deprecated_notice('load_plugin_manifest() is deprecated by ElggPlugin->getManifest()', 1.8); + + $xml_file = elgg_get_plugins_path() . "$plugin/manifest.xml"; + + try { + $manifest = new ElggPluginManifest($xml_file, $plugin); + } catch(Exception $e) { + return false; + } + + return $manifest->getManifest(); +} + +/** + * This function checks a plugin manifest 'elgg_version' value against the current install + * returning TRUE if the elgg_version is >= the current install's version. + * + * @deprecated 1.8 Use ElggPlugin->canActivate() + * + * @param string $manifest_elgg_version_string The build version (eg 2009010201). + * @return bool + */ +function check_plugin_compatibility($manifest_elgg_version_string) { + elgg_deprecated_notice('check_plugin_compatibility() is deprecated by ElggPlugin->canActivate()', 1.8); + + $version = get_version(); + + if (strpos($manifest_elgg_version_string, '.') === false) { + // Using version + $req_version = (int)$manifest_elgg_version_string; + + return ($version >= $req_version); + } + + return false; +} + +/** + * Shorthand function for finding the plugin settings. + * + * @deprecated 1.8 Use elgg_get_calling_plugin_entity() or elgg_get_plugin_from_id() + * + * @param string $plugin_id Optional plugin id, if not specified + * then it is detected from where you are calling. + * + * @return mixed + */ +function find_plugin_settings($plugin_id = null) { + elgg_deprecated_notice('find_plugin_setting() is deprecated by elgg_get_calling_plugin_entity() or elgg_get_plugin_from_id()', 1.8); + if ($plugin_id) { + return elgg_get_plugin_from_id($plugin_id); + } else { + return elgg_get_calling_plugin_entity(); + } +} + +/** + * Return an array of installed plugins. + * + * @deprecated 1.8 use elgg_get_plugins() + * + * @param string $status any|enabled|disabled + * @return array + */ +function get_installed_plugins($status = 'all') { + global $CONFIG; + + elgg_deprecated_notice('get_installed_plugins() was deprecated by elgg_get_plugins()', 1.8); + + $plugins = elgg_get_plugins($status); + + if (!$plugins) { + return array(); + } + + $installed_plugins = array(); + + foreach ($plugins as $plugin) { + if (!$plugin->isValid()) { + continue; + } + + $include = true; + + if ($status == 'enabled' && !$plugin->isActive()) { + $include = false; + } elseif ($status == 'disabled' && $plugin->isActive()) { + $include = true; + } + + if ($include) { + $installed_plugins[$plugin->getID()] = array( + 'active' => $plugin->isActive(), + 'manifest' => $plugin->getManifest()->getManifest() + ); + } + } + + return $installed_plugins; +} + +/** + * Enable a plugin for a site (default current site) + * + * Important: You should regenerate simplecache and the viewpath cache after executing this function + * otherwise you may experience view display artifacts. Do this with the following code: + * + * elgg_regenerate_simplecache(); + * elgg_reset_system_cache(); + * + * @deprecated 1.8 Use ElggPlugin->activate() + * + * @param string $plugin The plugin name. + * @param int $site_guid The site id, if not specified then this is detected. + * + * @return array + * @throws InvalidClassException + */ +function enable_plugin($plugin, $site_guid = null) { + elgg_deprecated_notice('enable_plugin() was deprecated by ElggPlugin->activate()', 1.8); + + $plugin = sanitise_string($plugin); + + $site_guid = (int) $site_guid; + if (!$site_guid) { + $site = get_config('site'); + $site_guid = $site->guid; + } + + try { + $plugin = new ElggPlugin($plugin); + } catch(Exception $e) { + return false; + } + + if (!$plugin->canActivate($site_guid)) { + return false; + } + + return $plugin->activate($site_guid); +} + +/** + * Disable a plugin for a site (default current site) + * + * Important: You should regenerate simplecache and the viewpath cache after executing this function + * otherwise you may experience view display artifacts. Do this with the following code: + * + * elgg_regenerate_simplecache(); + * elgg_reset_system_cache(); + * + * @deprecated 1.8 Use ElggPlugin->deactivate() + * + * @param string $plugin The plugin name. + * @param int $site_guid The site id, if not specified then this is detected. + * + * @return bool + * @throws InvalidClassException + */ +function disable_plugin($plugin, $site_guid = 0) { + elgg_deprecated_notice('disable_plugin() was deprecated by ElggPlugin->deactivate()', 1.8); + + $plugin = sanitise_string($plugin); + + $site_guid = (int) $site_guid; + if (!$site_guid) { + $site = get_config('site'); + $site_guid = $site->guid; + } + + try { + $plugin = new ElggPlugin($plugin); + } catch(Exception $e) { + return false; + } + + return $plugin->deactivate($site_guid); +} + +/** + * Return whether a plugin is enabled or not. + * + * @deprecated 1.8 Use elgg_is_active_plugin() + * + * @param string $plugin The plugin name. + * @param int $site_guid The site id, if not specified then this is detected. + * + * @return bool + */ +function is_plugin_enabled($plugin, $site_guid = 0) { + elgg_deprecated_notice('is_plugin_enabled() was deprecated by elgg_is_active_plugin()', 1.8); + return elgg_is_active_plugin($plugin, $site_guid); +} + +/** + * Get entities based on their private data. + * + * @param string $name The name of the setting + * @param string $value The value of the setting + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param string $order_by The field to order by; by default, time_created desc + * @param int $limit The number of entities to return; 10 by default + * @param int $offset The indexing offset, 0 by default + * @param boolean $count Return a count of entities + * @param int $site_guid The site to get entities for. 0 for current, -1 for any + * @param mixed $container_guid The container(s) GUIDs + * + * @return array A list of entities. + * @deprecated 1.8 Use elgg_get_entities_from_private_settings() + */ +function get_entities_from_private_setting($name = "", $value = "", $type = "", $subtype = "", +$owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, +$container_guid = null) { + elgg_deprecated_notice('get_entities_from_private_setting() was deprecated by elgg_get_entities_from_private_setting()!', 1.8); + + $options = array(); + + $options['private_setting_name'] = $name; + $options['private_setting_value'] = $value; + + // set container_guid to owner_guid to emulate old functionality + if ($owner_guid != "") { + if (is_null($container_guid)) { + $container_guid = $owner_guid; + } + } + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($container_guid) { + if (is_array($container_guid)) { + $options['container_guids'] = $container_guid; + } else { + $options['container_guid'] = $container_guid; + } + } + + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_private_settings($options); +} + +/** + * Get entities based on their private data by multiple keys. + * + * @param string $name The name of the setting + * @param mixed $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid The GUID of the owning user + * @param string $order_by The field to order by; by default, time_created desc + * @param int $limit The number of entities to return; 10 by default + * @param int $offset The indexing offset, 0 by default + * @param bool $count Count entities + * @param int $site_guid Site GUID. 0 for current, -1 for any. + * @param mixed $container_guid Container GUID + * + * @return array A list of entities. + * @deprecated 1.8 Use elgg_get_entities_from_private_settings() + */ +function get_entities_from_private_setting_multi(array $name, $type = "", $subtype = "", +$owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, +$site_guid = 0, $container_guid = null) { + + elgg_deprecated_notice('get_entities_from_private_setting_multi() was deprecated by elgg_get_entities_from_private_settings()!', 1.8); + + $options = array(); + + $pairs = array(); + foreach ($name as $setting_name => $setting_value) { + $pairs[] = array('name' => $setting_name, 'value' => $setting_value); + } + $options['private_setting_name_value_pairs'] = $pairs; + + // set container_guid to owner_guid to emulate old functionality + if ($owner_guid != "") { + if (is_null($container_guid)) { + $container_guid = $owner_guid; + } + } + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + if ($container_guid) { + if (is_array($container_guid)) { + $options['container_guids'] = $container_guid; + } else { + $options['container_guid'] = $container_guid; + } + } + + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_private_settings($options); +} + +/** + * Returns a viewable list of entities by relationship + * + * @see elgg_view_entity_list + * + * @deprecated 1.8 Use elgg_list_entities_from_relationship() + * + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Reverse the normal function of the query to instead say "give me all entities for whome $relationship_guid is a $relationship of" + * @param string $type The type of entity (eg 'object') + * @param string $subtype The entity subtype + * @param int $owner_guid The owner (default: all) + * @param int $limit The number of entities to display on a page + * @param true|false $fullview Whether or not to display the full view (default: true) + * @param true|false $viewtypetoggle Whether or not to allow gallery view + * @param true|false $pagination Whether to display pagination (default: true) + * @param bool $order_by SQL order by clause + * @return string The viewable list of entities + */ +function list_entities_from_relationship($relationship, $relationship_guid, +$inverse_relationship = false, $type = ELGG_ENTITIES_ANY_VALUE, +$subtype = ELGG_ENTITIES_ANY_VALUE, $owner_guid = 0, $limit = 10, +$fullview = true, $listtypetoggle = false, $pagination = true, $order_by = '') { + + elgg_deprecated_notice("list_entities_from_relationship was deprecated by elgg_list_entities_from_relationship()!", 1.8); + return elgg_list_entities_from_relationship(array( + 'relationship' => $relationship, + 'relationship_guid' => $relationship_guid, + 'inverse_relationship' => $inverse_relationship, + 'type' => $type, + 'subtype' => $subtype, + 'owner_guid' => $owner_guid, + 'order_by' => $order_by, + 'limit' => $limit, + 'full_view' => $fullview, + 'list_type_toggle' => $listtypetoggle, + 'pagination' => $pagination, + )); +} + +/** + * Gets the number of entities by a the number of entities related to them in a particular way. + * This is a good way to get out the users with the most friends, or the groups with the + * most members. + * + * @deprecated 1.8 Use elgg_get_entities_from_relationship_count() + * + * @param string $relationship The relationship eg "friends_of" + * @param bool $inverse_relationship Inverse relationship owners + * @param string $type The type of entity (default: all) + * @param string $subtype The entity subtype (default: all) + * @param int $owner_guid The owner of the entities (default: none) + * @param int $limit Limit + * @param int $offset Offset + * @param bool $count Return a count instead of entities + * @param int $site_guid Site GUID + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + */ +function get_entities_by_relationship_count($relationship, $inverse_relationship = true, $type = "", +$subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + elgg_deprecated_notice('get_entities_by_relationship_count() is deprecated by elgg_get_entities_from_relationship_count()', 1.8); + + $options = array(); + + $options['relationship'] = $relationship; + + // this used to default to true, which is wrong. + // flip it for the new function + $options['inverse_relationship'] = !$inverse_relationship; + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + $options['limit'] = $limit; + + if ($offset) { + $options['offset'] = $offset; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_relationship_count($options); +} + +/** + * Displays a human-readable list of entities + * + * @deprecated 1.8 Use elgg_list_entities_from_relationship_count() + * + * @param string $relationship The relationship eg "friends_of" + * @param bool $inverse_relationship Inverse relationship owners + * @param string $type The type of entity (eg 'object') + * @param string $subtype The entity subtype + * @param int $owner_guid The owner (default: all) + * @param int $limit The number of entities to display on a page + * @param bool $fullview Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow gallery view + * @param bool $pagination Whether to display pagination (default: true) + * + * @return string The viewable list of entities + */ +function list_entities_by_relationship_count($relationship, $inverse_relationship = true, +$type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, +$listtypetoggle = false, $pagination = true) { + + elgg_deprecated_notice('list_entities_by_relationship_count() was deprecated by elgg_list_entities_from_relationship_count()', 1.8); + + $options = array(); + + $options['relationship'] = $relationship; + + // this used to default to true, which is wrong. + // flip it for the new function + $options['inverse_relationship'] = !$inverse_relationship; + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + $options['limit'] = $limit; + + $options['full_view'] = $fullview; + + return elgg_list_entities_from_relationship_count($options); +} + +/** + * Gets the number of entities by a the number of entities related to + * them in a particular way also constrained by metadata. + * + * @deprecated 1.8 Use elgg_get_entities_from_relationship() + * + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Inverse relationship owner + * @param String $meta_name The metadata name + * @param String $meta_value The metadata value + * @param string $type The type of entity (default: all) + * @param string $subtype The entity subtype (default: all) + * @param int $owner_guid The owner of the entities (default: none) + * @param int $limit Limit + * @param int $offset Offset + * @param bool $count Return a count instead of entities + * @param int $site_guid Site GUID + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + */ +function get_entities_from_relationships_and_meta($relationship, $relationship_guid, +$inverse_relationship = false, $meta_name = "", $meta_value = "", $type = "", +$subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + + elgg_deprecated_notice('get_entities_from_relationship_and_meta() was deprecated by elgg_get_entities_from_relationship()!', 1.7); + + $options = array(); + + $options['relationship'] = $relationship; + $options['relationship_guid'] = $relationship_guid; + $options['inverse_relationship'] = $inverse_relationship; + + if ($meta_value) { + $options['values'] = $meta_value; + } + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($type) { + $options['types'] = $type; + } + + if ($subtype) { + $options['subtypes'] = $subtype; + } + + if ($owner_guid) { + $options['owner_guid'] = $owner_guid; + } + + if ($limit) { + $options['limit'] = $limit; + } + + if ($offset) { + $options['offset'] = $offset; + } + + if ($order_by) { + $options['order_by']; + } + + if ($site_guid) { + $options['site_guid']; + } + + if ($count) { + $options['count'] = $count; + } + + return elgg_get_entities_from_relationship($options); +} + + +/** + * Retrieves items from the river. All parameters are optional. + * + * @param int|array $subject_guid Acting entity to restrict to. Default: all + * @param int|array $object_guid Entity being acted on to restrict to. Default: all + * @param string $subject_relationship If set to a relationship type, this will use + * $subject_guid as the starting point and set the + * subjects to be all users this + * entity has this relationship with (eg 'friend'). + * Default: blank + * @param string $type The type of entity to restrict to. Default: all + * @param string $subtype The subtype of entity to restrict to. Default: all + * @param string $action_type The type of river action to restrict to. Default: all + * @param int $limit The number of items to retrieve. Default: 20 + * @param int $offset The page offset. Default: 0 + * @param int $posted_min The minimum time period to look at. Default: none + * @param int $posted_max The maximum time period to look at. Default: none + * + * @return array|false Depending on success + * @deprecated 1.8 Use elgg_get_river() + */ +function get_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', +$type = '', $subtype = '', $action_type = '', $limit = 20, $offset = 0, $posted_min = 0, +$posted_max = 0) { + elgg_deprecated_notice("get_river_items deprecated by elgg_get_river", 1.8); + + $options = array(); + + if ($subject_guid) { + $options['subject_guid'] = $subject_guid; + } + + if ($object_guid) { + $options['object_guid'] = $object_guid; + } + + if ($subject_relationship) { + $options['relationship'] = $subject_relationship; + unset($options['subject_guid']); + $options['relationship_guid'] = $subject_guid; + } + + if ($type) { + $options['type'] = $type; + } + + if ($subtype) { + $options['subtype'] = $subtype; + } + + if ($action_type) { + $options['action_type'] = $action_type; + } + + $options['limit'] = $limit; + $options['offset'] = $offset; + + if ($posted_min) { + $options['posted_time_lower'] = $posted_min; + } + + if ($posted_max) { + $options['posted_time_upper'] = $posted_max; + } + + return elgg_get_river($options); +} + +/** + * Returns a human-readable version of the river. + * + * @param int|array $subject_guid Acting entity to restrict to. Default: all + * @param int|array $object_guid Entity being acted on to restrict to. Default: all + * @param string $subject_relationship If set to a relationship type, this will use + * $subject_guid as the starting point and set + * the subjects to be all users this entity has this + * relationship with (eg 'friend'). Default: blank + * @param string $type The type of entity to restrict to. Default: all + * @param string $subtype The subtype of entity to restrict to. Default: all + * @param string $action_type The type of river action to restrict to. Default: all + * @param int $limit The number of items to retrieve. Default: 20 + * @param int $posted_min The minimum time period to look at. Default: none + * @param int $posted_max The maximum time period to look at. Default: none + * @param bool $pagination Show pagination? + * + * @return string Human-readable river. + * @deprecated 1.8 Use elgg_list_river() + */ +function elgg_view_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', +$type = '', $subtype = '', $action_type = '', $limit = 20, $posted_min = 0, +$posted_max = 0, $pagination = true) { + elgg_deprecated_notice("elgg_view_river_items deprecated for elgg_list_river", 1.8); + + $river_items = get_river_items($subject_guid, $object_guid, $subject_relationship, + $type, $subtype, $action_type, $limit + 1, $posted_min, $posted_max); + + // Get input from outside world and sanitise it + $offset = (int) get_input('offset', 0); + + // view them + $params = array( + 'items' => $river_items, + 'count' => count($river_items), + 'offset' => $offset, + 'limit' => $limit, + 'pagination' => $pagination, + 'list-class' => 'elgg-list-river', + ); + + return elgg_view('page/components/list', $params); +} + +/** + * Construct and execute the query required for the activity stream. + * + * @deprecated 1.8 This is outdated and uses the systemlog table instead of the river table. + * Don't use it. + */ +function get_activity_stream_data($limit = 10, $offset = 0, $type = "", $subtype = "", +$owner_guid = "", $owner_relationship = "") { + elgg_deprecated_notice("get_activity_stream_data was deprecated", 1.8); + + global $CONFIG; + + $limit = (int)$limit; + $offset = (int)$offset; + + if ($type) { + if (!is_array($type)) { + $type = array(sanitise_string($type)); + } else { + foreach ($type as $k => $v) { + $type[$k] = sanitise_string($v); + } + } + } + + if ($subtype) { + if (!is_array($subtype)) { + $subtype = array(sanitise_string($subtype)); + } else { + foreach ($subtype as $k => $v) { + $subtype[$k] = sanitise_string($v); + } + } + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + foreach ($owner_guid as $k => $v) { + $owner_guid[$k] = (int)$v; + } + } else { + $owner_guid = array((int)$owner_guid); + } + } + + $owner_relationship = sanitise_string($owner_relationship); + + // Get a list of possible views + $activity_events = array(); + $activity_views = array_merge(elgg_view_tree('activity', 'default'), + elgg_view_tree('river', 'default')); + + $done = array(); + + foreach ($activity_views as $view) { + $fragments = explode('/', $view); + $tmp = explode('/', $view, 2); + $tmp = $tmp[1]; + + if ((isset($fragments[0])) && (($fragments[0] == 'river') || ($fragments[0] == 'activity')) + && (!in_array($tmp, $done))) { + + if (isset($fragments[1])) { + $f = array(); + for ($n = 1; $n < count($fragments); $n++) { + $val = sanitise_string($fragments[$n]); + switch($n) { + case 1: $key = 'type'; break; + case 2: $key = 'subtype'; break; + case 3: $key = 'event'; break; + } + $f[$key] = $val; + } + + // Filter result based on parameters + $add = true; + if ($type) { + if (!in_array($f['type'], $type)) { + $add = false; + } + } + if (($add) && ($subtype)) { + if (!in_array($f['subtype'], $subtype)) { + $add = false; + } + } + if (($add) && ($event)) { + if (!in_array($f['event'], $event)) { + $add = false; + } + } + + if ($add) { + $activity_events[] = $f; + } + } + + $done[] = $tmp; + } + } + + $n = 0; + foreach ($activity_events as $details) { + // Get what we're talking about + if ($details['subtype'] == 'default') { + $details['subtype'] = ''; + } + + if (($details['type']) && ($details['event'])) { + if ($n > 0) { + $obj_query .= " or "; + } + + $access = ""; + if ($details['type'] != 'relationship') { + $access = " and " . get_access_sql_suffix('sl'); + } + + $obj_query .= "( sl.object_type='{$details['type']}' + AND sl.object_subtype='{$details['subtype']}' + AND sl.event='{$details['event']}' $access )"; + + $n++; + } + } + + // User + if ((count($owner_guid)) && ($owner_guid[0] != 0)) { + $user = " and sl.performed_by_guid in (" . implode(',', $owner_guid) . ")"; + + if ($owner_relationship) { + $friendsarray = ""; + if ($friends = elgg_get_entities_from_relationship(array( + 'relationship' => $owner_relationship, + 'relationship_guid' => $owner_guid[0], + 'inverse_relationship' => FALSE, + 'type' => 'user', + 'subtype' => $subtype, + 'limit' => false)) + ) { + + $friendsarray = array(); + foreach ($friends as $friend) { + $friendsarray[] = $friend->getGUID(); + } + + $user = " and sl.performed_by_guid in (" . implode(',', $friendsarray) . ")"; + } + } + } + + $query = "SELECT sl.* FROM {$CONFIG->dbprefix}system_log sl + WHERE 1 $user AND ($obj_query) + ORDER BY sl.time_created desc limit $offset, $limit"; + return get_data($query); +} + +/** + * Perform standard authentication with a given username and password. + * Returns an ElggUser object for use with login. + * + * @see login + * + * @param string $username The username, optionally (for standard logins) + * @param string $password The password, optionally (for standard logins) + * + * @return ElggUser|false The authenticated user object, or false on failure. + * + * @deprecated 1.8 Use elgg_authenticate + */ +function authenticate($username, $password) { + elgg_deprecated_notice('authenticate() has been deprecated for elgg_authenticate()', 1.8); + $pam = new ElggPAM('user'); + $credentials = array('username' => $username, 'password' => $password); + $result = $pam->authenticate($credentials); + if ($result) { + return get_user_by_username($username); + } + return false; +} + + +/** + * Get the members of a site. + * + * @param int $site_guid Site GUID + * @param int $limit User GUID + * @param int $offset Offset + * + * @return mixed + * @deprecated 1.8 Use ElggSite::getMembers() + */ +function get_site_members($site_guid, $limit = 10, $offset = 0) { + elgg_deprecated_notice("get_site_members() deprecated. + Use ElggSite::getMembers()", 1.8); + + $site = get_entity($site_guid); + if ($site) { + return $site->getMembers($limit, $offset); + } + + return false; +} + +/** + * Display a list of site members + * + * @param int $site_guid The GUID of the site + * @param int $limit The number of members to display on a page + * @param bool $fullview Whether or not to display the full view (default: true) + * + * @return string A displayable list of members + * @deprecated 1.8 Use ElggSite::listMembers() + */ +function list_site_members($site_guid, $limit = 10, $fullview = true) { + elgg_deprecated_notice("list_site_members() deprecated. + Use ElggSite::listMembers()", 1.8); + + $options = array( + 'limit' => $limit, + 'full_view' => $full_view, + ); + + $site = get_entity($site_guid); + if ($site) { + return $site->listMembers($options); + } + + return ''; +} + + +/** + * Add a collection to a site. + * + * @param int $site_guid Site GUID + * @param int $collection_guid Collection GUID + * + * @return mixed + * @deprecated 1.8 Don't use this. + */ +function add_site_collection($site_guid, $collection_guid) { + elgg_deprecated_notice("add_site_collection has been deprecated", 1.8); + global $CONFIG; + + $site_guid = (int)$site_guid; + $collection_guid = (int)$collection_guid; + + return add_entity_relationship($collection_guid, "member_of_site", $site_guid); +} + +/** + * Remove a collection from a site. + * + * @param int $site_guid Site GUID + * @param int $collection_guid Collection GUID + * + * @return mixed + * @deprecated 1.8 Don't use this. + */ +function remove_site_collection($site_guid, $collection_guid) { + elgg_deprecated_notice("remove_site_collection has been deprecated", 1.8); + $site_guid = (int)$site_guid; + $collection_guid = (int)$collection_guid; + + return remove_entity_relationship($collection_guid, "member_of_site", $site_guid); +} + +/** + * Get the collections belonging to a site. + * + * @param int $site_guid Site GUID + * @param string $subtype Subtype + * @param int $limit Limit + * @param int $offset Offset + * + * @return mixed + * @deprecated 1.8 Don't use this. + */ +function get_site_collections($site_guid, $subtype = "", $limit = 10, $offset = 0) { + elgg_deprecated_notice("get_site_collections has been deprecated", 1.8); + $site_guid = (int)$site_guid; + $subtype = sanitise_string($subtype); + $limit = (int)$limit; + $offset = (int)$offset; + + // collection isn't a valid type. This won't work. + return elgg_get_entities_from_relationship(array( + 'relationship' => 'member_of_site', + 'relationship_guid' => $site_guid, + 'inverse_relationship' => TRUE, + 'type' => 'collection', + 'subtype' => $subtype, + 'limit' => $limit, + 'offset' => $offset + )); +} + +/** + * Get an array of tags with weights for use with the output/tagcloud view. + * + * @deprecated 1.8 Use elgg_get_tags(). + * + * @param int $threshold Get the threshold of minimum number of each tags to + * bother with (ie only show tags where there are more + * than $threshold occurances) + * @param int $limit Number of tags to return + * @param string $metadata_name Optionally, the name of the field you want to grab for + * @param string $entity_type Optionally, the entity type ('object' etc) + * @param string $entity_subtype The entity subtype, optionally + * @param int $owner_guid The GUID of the tags owner, optionally + * @param int $site_guid Optionally, the site to restrict to (default is the current site) + * @param int $start_ts Optionally specify a start timestamp for tags used to + * generate cloud. + * @param int $end_ts Optionally specify an end timestamp for tags used to generate cloud + * + * @return array|false Array of objects with ->tag and ->total values, or false on failure + */ +function get_tags($threshold = 1, $limit = 10, $metadata_name = "", $entity_type = "object", +$entity_subtype = "", $owner_guid = "", $site_guid = -1, $start_ts = "", $end_ts = "") { + + elgg_deprecated_notice('get_tags() has been replaced by elgg_get_tags()', 1.8); + + if (is_array($metadata_name)) { + return false; + } + + $options = array(); + if ($metadata_name === '') { + $options['tag_names'] = array(); + } else { + $options['tag_names'] = array($metadata_name); + } + + $options['threshold'] = $threshold; + $options['limit'] = $limit; + + // rewrite owner_guid to container_guid to emulate old functionality + $container_guid = $owner_guid; + if ($container_guid) { + $options['container_guids'] = $container_guid; + } + + if ($entity_type) { + $options['type'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtype'] = $entity_subtype; + } + + if ($site_guid != -1) { + $options['site_guids'] = $site_guid; + } + + if ($end_ts) { + $options['created_time_upper'] = $end_ts; + } + + if ($start_ts) { + $options['created_time_lower'] = $start_ts; + } + + $r = elgg_get_tags($options); + return $r; +} + +/** + * Loads and displays a tagcloud given particular criteria. + * + * @deprecated 1.8 use elgg_view_tagcloud() + * + * @param int $threshold Get the threshold of minimum number of each tags + * to bother with (ie only show tags where there are + * more than $threshold occurances) + * @param int $limit Number of tags to return + * @param string $metadata_name Optionally, the name of the field you want to grab for + * @param string $entity_type Optionally, the entity type ('object' etc) + * @param string $entity_subtype The entity subtype, optionally + * @param int $owner_guid The GUID of the tags owner, optionally + * @param int $site_guid Optionally, the site to restrict to (default is the current site) + * @param int $start_ts Optionally specify a start timestamp for tags used to + * generate cloud. + * @param int $end_ts Optionally specify an end timestamp for tags used to generate + * cloud. + * + * @return string The HTML (or other, depending on view type) of the tagcloud. + */ +function display_tagcloud($threshold = 1, $limit = 10, $metadata_name = "", $entity_type = "object", +$entity_subtype = "", $owner_guid = "", $site_guid = -1, $start_ts = "", $end_ts = "") { + + elgg_deprecated_notice('display_tagcloud() was deprecated by elgg_view_tagcloud()!', 1.8); + + $tags = get_tags($threshold, $limit, $metadata_name, $entity_type, + $entity_subtype, $owner_guid, $site_guid, $start_ts, $end_ts); + + return elgg_view('output/tagcloud', array( + 'value' => $tags, + 'type' => $entity_type, + 'subtype' => $entity_subtype, + )); +} + + +/** + * Obtains a list of objects owned by a user + * + * @param int $user_guid The GUID of the owning user + * @param string $subtype Optionally, the subtype of objects + * @param int $limit The number of results to return (default 10) + * @param int $offset Indexing offset, if any + * @param int $timelower The earliest time the entity can have been created. Default: all + * @param int $timeupper The latest time the entity can have been created. Default: all + * + * @return false|array An array of ElggObjects or false, depending on success + * @deprecated 1.8 Use elgg_get_entities() instead + */ +function get_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$offset = 0, $timelower = 0, $timeupper = 0) { + elgg_deprecated_notice("get_user_objects() was deprecated in favor of elgg_get_entities()", 1.8); + $ntt = elgg_get_entities(array( + 'type' => 'object', + 'subtype' => $subtype, + 'owner_guid' => $user_guid, + 'limit' => $limit, + 'offset' => $offset, + 'container_guid' => $user_guid, + 'created_time_lower' => $timelower, + 'created_time_upper' => $timeupper + )); + return $ntt; +} + +/** + * Counts the objects (optionally of a particular subtype) owned by a user + * + * @param int $user_guid The GUID of the owning user + * @param string $subtype Optionally, the subtype of objects + * @param int $timelower The earliest time the entity can have been created. Default: all + * @param int $timeupper The latest time the entity can have been created. Default: all + * + * @return int The number of objects the user owns (of this subtype) + * @deprecated 1.8 Use elgg_get_entities() instead + */ +function count_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $timelower = 0, +$timeupper = 0) { + elgg_deprecated_notice("count_user_objects() was deprecated in favor of elgg_get_entities()", 1.8); + $total = elgg_get_entities(array( + 'type' => 'object', + 'subtype' => $subtype, + 'owner_guid' => $user_guid, + 'count' => TRUE, + 'container_guid' => $user_guid, + 'created_time_lower' => $timelower, + 'created_time_upper' => $timeupper + )); + return $total; +} + +/** + * Displays a list of user objects of a particular subtype, with navigation. + * + * @see elgg_view_entity_list + * + * @param int $user_guid The GUID of the user + * @param string $subtype The object subtype + * @param int $limit The number of entities to display on a page + * @param bool $fullview Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow gallery view (default: true) + * @param bool $pagination Whether to display pagination (default: true) + * @param int $timelower The earliest time the entity can have been created. Default: all + * @param int $timeupper The latest time the entity can have been created. Default: all + * + * @return string The list in a form suitable to display + * @deprecated 1.8 Use elgg_list_entities() instead + */ +function list_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, +$fullview = true, $listtypetoggle = true, $pagination = true, $timelower = 0, $timeupper = 0) { + elgg_deprecated_notice("list_user_objects() was deprecated in favor of elgg_list_entities()", 1.8); + + $offset = (int) get_input('offset'); + $limit = (int) $limit; + $count = (int) count_user_objects($user_guid, $subtype, $timelower, $timeupper); + $entities = get_user_objects($user_guid, $subtype, $limit, $offset, $timelower, $timeupper); + + return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $listtypetoggle, + $pagination); +} + + +/** + * Get user objects by an array of metadata + * + * @param int $user_guid The GUID of the owning user + * @param string $subtype Optionally, the subtype of objects + * @param array $metadata An array of metadata + * @param int $limit The number of results to return (default 10) + * @param int $offset Indexing offset, if any + * + * @return false|array An array of ElggObjects or false, depending on success + * @deprecated 1.8 Use elgg_get_entities_from_metadata() instead + */ +function get_user_objects_by_metadata($user_guid, $subtype = "", $metadata = array(), +$limit = 0, $offset = 0) { + elgg_deprecated_notice("get_user_objects_by_metadata() was deprecated in favor of elgg_get_entities_from_metadata()", 1.8); + return get_entities_from_metadata_multi($metadata, "object", $subtype, $user_guid, + $limit, $offset); +} + +/** + * Set the validation status for a user. + * + * @param bool $status Validated (true) or false + * @param string $method Optional method to say how a user was validated + * @return bool + * @deprecated 1.8 Use elgg_set_user_validation_status() + */ +function set_user_validation_status($user_guid, $status, $method = '') { + elgg_deprecated_notice("set_user_validation_status() is deprecated", 1.8); + return elgg_set_user_validation_status($user_guid, $status, $method); +} + +/** + * Trigger an event requesting that a user guid be validated somehow - either by email address or some other way. + * + * This function invalidates any existing validation value. + * + * @param int $user_guid User's GUID + * @deprecated 1.8 Hook into the register, user plugin hook and request validation. + */ +function request_user_validation($user_guid) { + elgg_deprecated_notice("request_user_validation() is deprecated. + Plugins should register for the 'register, user' plugin hook", 1.8); + $user = get_entity($user_guid); + + if (($user) && ($user instanceof ElggUser)) { + // invalidate any existing validations + set_user_validation_status($user_guid, false); + + // request validation + trigger_elgg_event('validate', 'user', $user); + } +} + +/** + * Register a user settings page with the admin panel. + * This function extends the view "usersettings/main" with the provided view. + * This view should provide a description and either a control or a link to. + * + * Usage: + * - To add a control to the main admin panel then extend usersettings/main + * - To add a control to a new page create a page which renders a view + * usersettings/subpage (where subpage is your new page - + * nb. some pages already exist that you can extend), extend the main view + * to point to it, and add controls to your new view. + * + * At the moment this is essentially a wrapper around elgg_extend_view(). + * + * @param string $new_settings_view The view associated with the control you're adding + * @param string $view The view to extend, by default this is 'usersettings/main'. + * @param int $priority Optional priority to govern the appearance in the list. + * + * @return bool + * @deprecated 1.8 Extend one of the views in core/settings + */ +function extend_elgg_settings_page($new_settings_view, $view = 'usersettings/main', +$priority = 500) { + // see views: /core/settings + elgg_deprecated_notice("extend_elgg_settings_page has been deprecated. Extend one of the settings views instead", 1.8); + + return elgg_extend_view($view, $new_settings_view, $priority); +} + +/** + * Returns a representation of a full 'page' (which might be an HTML page, + * RSS file, etc, depending on the current viewtype) + * + * @param string $title + * @param string $body + * @return string + * + * @deprecated 1.8 Use elgg_view_page() + */ +function page_draw($title, $body, $sidebar = "") { + elgg_deprecated_notice("page_draw() was deprecated in favor of elgg_view_page() in 1.8.", 1.8); + + $vars = array( + 'sidebar' => $sidebar + ); + echo elgg_view_page($title, $body, 'default', $vars); +} + +/** + * Wrapper function to display search listings. + * + * @param string $icon The icon for the listing + * @param string $info Any information that needs to be displayed. + * + * @return string The HTML (etc) representing the listing + * @deprecated 1.8 use elgg_view_image_block() + */ +function elgg_view_listing($icon, $info) { + elgg_deprecated_notice('elgg_view_listing deprecated by elgg_view_image_block', 1.8); + return elgg_view('page/components/image_block', array('image' => $icon, 'body' => $info)); +} + +/** + * Return the icon URL for an entity. + * + * @tip Can be overridden by registering a plugin hook for entity:icon:url, $entity_type. + * + * @internal This is passed an entity rather than a guid to handle non-created entities. + * + * @param ElggEntity $entity The entity + * @param string $size Icon size + * + * @return string URL to the entity icon. + * @deprecated 1.8 Use $entity->getIconURL() + */ +function get_entity_icon_url(ElggEntity $entity, $size = 'medium') { + elgg_deprecated_notice("get_entity_icon_url() deprecated for getIconURL()", 1.8); + global $CONFIG; + + $size = sanitise_string($size); + switch (strtolower($size)) { + case 'master': + $size = 'master'; + break; + + case 'large' : + $size = 'large'; + break; + + case 'topbar' : + $size = 'topbar'; + break; + + case 'tiny' : + $size = 'tiny'; + break; + + case 'small' : + $size = 'small'; + break; + + case 'medium' : + default: + $size = 'medium'; + } + + $url = false; + + $viewtype = elgg_get_viewtype(); + + // Step one, see if anyone knows how to render this in the current view + $params = array('entity' => $entity, 'viewtype' => $viewtype, 'size' => $size); + $url = elgg_trigger_plugin_hook('entity:icon:url', $entity->getType(), $params, $url); + + // Fail, so use default + if (!$url) { + $type = $entity->getType(); + $subtype = $entity->getSubtype(); + + if (!empty($subtype)) { + $overrideurl = elgg_view("icon/{$type}/{$subtype}/{$size}", array('entity' => $entity)); + if (!empty($overrideurl)) { + return $overrideurl; + } + } + + $overrideurl = elgg_view("icon/{$type}/default/{$size}", array('entity' => $entity)); + if (!empty($overrideurl)) { + return $overrideurl; + } + + $url = "_graphics/icons/default/$size.png"; + } + + return elgg_normalize_url($url); +} + +/** + * Return the current logged in user, or NULL if no user is logged in. + * + * If no user can be found in the current session, a plugin + * hook - 'session:get' 'user' to give plugin authors another + * way to provide user details to the ACL system without touching the session. + * + * @deprecated 1.8 Use elgg_get_logged_in_user_entity() + * @return ElggUser|NULL + */ +function get_loggedin_user() { + elgg_deprecated_notice('get_loggedin_user() is deprecated by elgg_get_logged_in_user_entity()', 1.8); + return elgg_get_logged_in_user_entity(); +} + +/** + * Return the current logged in user by id. + * + * @deprecated 1.8 Use elgg_get_logged_in_user_guid() + * @see elgg_get_logged_in_user_entity() + * @return int + */ +function get_loggedin_userid() { + elgg_deprecated_notice('get_loggedin_userid() is deprecated by elgg_get_logged_in_user_guid()', 1.8); + return elgg_get_logged_in_user_guid(); +} + + +/** + * Returns whether or not the user is currently logged in + * + * @deprecated 1.8 Use elgg_is_logged_in(); + * @return bool + */ +function isloggedin() { + elgg_deprecated_notice('isloggedin() is deprecated by elgg_is_logged_in()', 1.8); + return elgg_is_logged_in(); +} + +/** + * Returns whether or not the user is currently logged in and that they are an admin user. + * + * @deprecated 1.8 Use elgg_is_admin_logged_in() + * @return bool + */ +function isadminloggedin() { + elgg_deprecated_notice('isadminloggedin() is deprecated by elgg_is_admin_logged_in()', 1.8); + return elgg_is_admin_logged_in(); +} + + +/** + * Loads plugins + * + * @deprecated 1.8 Use elgg_load_plugins() + * + * @return bool + */ +function load_plugins() { + elgg_deprecated_notice('load_plugins() is deprecated by elgg_load_plugins()', 1.8); + return elgg_load_plugins(); +} + +/** + * Find the plugin settings for a user. + * + * @param string $plugin_id Plugin name. + * @param int $user_guid The guid who's settings to retrieve. + * + * @deprecated 1.8 Use elgg_get_all_plugin_user_settings() or ElggPlugin->getAllUserSettings() + * @return StdClass Object with all user settings. + */ +function find_plugin_usersettings($plugin_id = null, $user_guid = 0) { + elgg_deprecated_notice('find_plugin_usersettings() is deprecated by elgg_get_all_plugin_user_settings()', 1.8); + return elgg_get_all_plugin_user_settings($user_guid, $plugin_id, true); +} + +/** + * Set a user specific setting for a plugin. + * + * @param string $name The name - note, can't be "title". + * @param mixed $value The value. + * @param int $user_guid Optional user. + * @param string $plugin_id Optional plugin name, if not specified then it + * is detected from where you are calling from. + * + * @return bool + * @deprecated 1.8 Use elgg_set_plugin_user_setting() or ElggPlugin->setUserSetting() + */ +function set_plugin_usersetting($name, $value, $user_guid = 0, $plugin_id = "") { + elgg_deprecated_notice('find_plugin_usersettings() is deprecated by elgg_get_all_plugin_user_settings()', 1.8); + return elgg_set_plugin_user_setting($name, $value, $user_guid, $plugin_id); +} + +/** + * Clears a user-specific plugin setting + * + * @param str $name Name of the plugin setting + * @param int $user_guid Defaults to logged in user + * @param str $plugin_id Defaults to contextual plugin name + * + * @deprecated 1.8 Use elgg_unset_plugin_user_setting or ElggPlugin->unsetUserSetting(). + * @return bool Success + */ +function clear_plugin_usersetting($name, $user_guid = 0, $plugin_id = '') { + elgg_deprecated_notice('clear_plugin_usersetting() is deprecated by elgg_unset_plugin_usersetting()', 1.8); + return elgg_unset_plugin_user_setting($name, $user_guid, $plugin_id); +} + +/** + * Get a user specific setting for a plugin. + * + * @param string $name The name. + * @param int $user_guid Guid of owning user + * @param string $plugin_id Optional plugin name, if not specified + * it is detected from where you are calling. + * + * @deprecated 1.8 Use elgg_get_plugin_user_setting() or ElggPlugin->getUserSetting() + * @return mixed + */ +function get_plugin_usersetting($name, $user_guid = 0, $plugin_id = "") { + elgg_deprecated_notice('get_plugin_usersetting() is deprecated by elgg_get_plugin_user_setting()', 1.8); + return elgg_get_plugin_user_setting($name, $user_guid, $plugin_id); +} + +/** + * Set a setting for a plugin. + * + * @param string $name The name - note, can't be "title". + * @param mixed $value The value. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @deprecated 1.8 Use elgg_set_plugin_setting() or ElggPlugin->setSetting() + * @return int|false + */ +function set_plugin_setting($name, $value, $plugin_id = null) { + elgg_deprecated_notice('set_plugin_setting() is deprecated by elgg_set_plugin_setting()', 1.8); + return elgg_set_plugin_setting($name, $value, $plugin_id); +} + +/** + * Get setting for a plugin. + * + * @param string $name The name. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @deprecated 1.8 Use elgg_get_plugin_setting() or ElggPlugin->getSetting() + * @return mixed + */ +function get_plugin_setting($name, $plugin_id = "") { + elgg_deprecated_notice('get_plugin_setting() is deprecated by elgg_get_plugin_setting()', 1.8); + return elgg_get_plugin_setting($name, $plugin_id); +} + +/** + * Clear a plugin setting. + * + * @param string $name The name. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @deprecated 1.8 Use elgg_unset_plugin_setting() or ElggPlugin->unsetSetting() + * @return bool + */ +function clear_plugin_setting($name, $plugin_id = "") { + elgg_deprecated_notice('clear_plugin_setting() is deprecated by elgg_unset_plugin_setting()', 1.8); + return elgg_unset_plugin_setting($name, $plugin_id); +} + +/** + * Unsets all plugin settings for a plugin. + * + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. + * + * @return bool + * @deprecated 1.8 Use elgg_unset_all_plugin_settings() or ElggPlugin->unsetAllSettings() + * @since 1.7.0 + */ +function clear_all_plugin_settings($plugin_id = "") { + elgg_deprecated_notice('clear_all_plugin_settings() is deprecated by elgg_unset_all_plugin_setting()', 1.8); + return elgg_unset_all_plugin_settings($plugin_id); +} + + +/** + * Get a list of annotations for a given object/user/annotation type. + * + * @param int|array $entity_guid GUID to return annotations of (falsey for any) + * @param string $entity_type Type of entity + * @param string $entity_subtype Subtype of entity + * @param string $name Name of annotation + * @param mixed $value Value of annotation + * @param int|array $owner_guid Owner(s) of annotation + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Order annotations by SQL + * @param int $timelower Lower time limit + * @param int $timeupper Upper time limit + * @param int $entity_owner_guid Owner guid for the entity + * + * @return array + * @deprecated 1.8 Use elgg_get_annotations() + */ +function get_annotations($entity_guid = 0, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "asc", $timelower = 0, +$timeupper = 0, $entity_owner_guid = 0) { + + elgg_deprecated_notice('get_annotations() is deprecated by elgg_get_annotations()', 1.8); + $options = array(); + + if ($entity_guid) { + $options['guid'] = $entity_guid; + } + + if ($entity_type) { + $options['type'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtype'] = $entity_subtype; + } + + if ($name) { + $options['annotation_name'] = $name; + } + + if ($value) { + $options['annotation_value'] = $value; + } + + if ($owner_guid) { + $options['annotation_owner_guid'] = $owner_guid; + } + + $options['limit'] = $limit; + $options['offset'] = $offset; + + if ($order_by == 'desc') { + $options['order_by'] = 'n_table.time_created desc'; + } + + if ($timelower) { + $options['annotation_time_lower'] = $timelower; + } + + if ($timeupper) { + $options['annotation_time_upper'] = $timeupper; + } + + if ($entity_owner_guid) { + $options['owner_guid'] = $entity_owner_guid; + } + + return elgg_get_annotations($options); +} + + +/** + * Returns a human-readable list of annotations on a particular entity. + * + * @param int $entity_guid The entity GUID + * @param string $name The name of the kind of annotation + * @param int $limit The number of annotations to display at once + * @param true|false $asc Display annotations in ascending order. (Default: true) + * + * @return string HTML (etc) version of the annotation list + * @deprecated 1.8 Use elgg_list_annotations() + */ +function list_annotations($entity_guid, $name = "", $limit = 25, $asc = true) { + elgg_deprecated_notice('list_annotations() is deprecated by elgg_list_annotations()', 1.8); + + if ($asc) { + $asc = "asc"; + } else { + $asc = "desc"; + } + + $options = array( + 'guid' => $entity_guid, + 'limit' => $limit, + 'order_by' => "n_table.time_created $asc" + ); + + return elgg_list_annotations($options); +} + +/** + * Helper function to deprecate annotation calculation functions. Don't use. + * + * @param unknown_type $entity_guid + * @param unknown_type $entity_type + * @param unknown_type $entity_subtype + * @param unknown_type $name + * @param unknown_type $value + * @param unknown_type $value_type + * @param unknown_type $owner_guid + * @param unknown_type $timelower + * @param unknown_type $timeupper + * @param unknown_type $calculation + * @internal Don't use this at all. + * @deprecated 1.8 Use elgg_get_annotations() + */ +function elgg_deprecated_annotation_calculation($entity_guid = 0, $entity_type = "", $entity_subtype = "", +$name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, +$timeupper = 0, $calculation = '') { + + $options = array('annotation_calculation' => $calculation); + + if ($entity_guid) { + $options['guid'] = $entity_guid; + } + + if ($entity_type) { + $options['type'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtype'] = $entity_subtype; + } + + if ($name) { + $options['annotation_name'] = $name; + } + + if ($value) { + $options['annotation_value'] = $value; + } + + if ($owner_guid) { + $options['annotation_owner_guid'] = $owner_guid; + } + + if ($order_by == 'desc') { + $options['order_by'] = 'n_table.time_created desc'; + } + + if ($timelower) { + $options['annotation_time_lower'] = $timelower; + } + + if ($timeupper) { + $options['annotation_time_upper'] = $timeupper; + } + + return elgg_get_annotations($options); +} + +/** + * Count the number of annotations based on search parameters + * + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * @param int $timelower Lower time limit + * @param int $timeupper Upper time limit + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'count' => true + * @return int + */ +function count_annotations($entity_guid = 0, $entity_type = "", $entity_subtype = "", +$name = "", $value = "", $value_type = "", $owner_guid = 0, $timelower = 0, +$timeupper = 0) { + elgg_deprecated_notice('count_annotations() is deprecated by elgg_get_annotations() and passing "count" => true', 1.8); + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'count'); +} + +/** + * Return the sum of a given integer annotation. + * + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'sum' + * @return int + */ +function get_annotations_sum($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { + elgg_deprecated_notice('get_annotations_sum() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "sum"', 1.8); + + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'sum'); +} + +/** + * Return the max of a given integer annotation. + * + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'max' + * @return int + */ +function get_annotations_max($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { + elgg_deprecated_notice('get_annotations_max() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "max"', 1.8); + + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'max'); +} + + +/** + * Return the minumum of a given integer annotation. + * + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'min' + * @return int + */ +function get_annotations_min($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { + elgg_deprecated_notice('get_annotations_min() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "min"', 1.8); + + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'min'); +} + + +/** + * Return the average of a given integer annotation. + * + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * + * @deprecated 1.8 Use elgg_get_annotations() and pass 'annotation_calculation' => 'min' + * + * @return int + */ +function get_annotations_avg($entity_guid, $entity_type = "", $entity_subtype = "", $name = "", +$value = "", $value_type = "", $owner_guid = 0) { + elgg_deprecated_notice('get_annotations_avg() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "avg"', 1.8); + + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, 'avg'); +} + + +/** + * Perform a mathmatical calculation on integer annotations. + * + * @param string $sum What sort of calculation to perform + * @param int $entity_guid Guid of Entity + * @param string $entity_type Type of Entity + * @param string $entity_subtype Subtype of Entity + * @param string $name Name of annotation + * @param string $value Value of annotation + * @param string $value_type Type of value + * @param int $owner_guid GUID of owner of annotation + * @param int $timelower Lower time limit + * @param int $timeupper Upper time limit + * + * @return int + * @deprecated 1.8 Use elgg_get_annotations() and pass anntoation_calculation => <calculation> + */ +function get_annotations_calculate_x($sum = "avg", $entity_guid, $entity_type = "", +$entity_subtype = "", $name = "", $value = "", $value_type = "", $owner_guid = 0, +$timelower = 0, $timeupper = 0) { + elgg_deprecated_notice('get_annotations_calculate_x() is deprecated by elgg_get_annotations() and passing "annotation_calculation" => "calculation"', 1.8); + + return elgg_deprecated_annotation_calculation($entity_guid, $entity_type, $entity_subtype, + $name, $value, $value_type, $owner_guid, $timelower, $timeupper, $sum); +} + + +/** + * Lists entities by the totals of a particular kind of annotation AND + * the value of a piece of metadata + * + * @param string $entity_type Type of entity. + * @param string $entity_subtype Subtype of entity. + * @param string $name Name of annotation. + * @param string $mdname Metadata name + * @param string $mdvalue Metadata value + * @param int $limit Maximum number of results to return. + * @param int $owner_guid Owner. + * @param int $group_guid Group container. Currently only supported if entity_type is object + * @param boolean $asc Whether to list in ascending or descending order (default: desc) + * @param boolean $fullview Whether to display the entities in full + * @param boolean $listtypetoggle Can the 'gallery' view can be displayed (default: no) + * @param boolean $pagination Display pagination + * @param string $orderdir 'desc' or 'asc' + * + * @deprecated 1.8 Use elgg_list_entities_from_annotation_calculation(). + * + * @return string Formatted entity list + */ +function list_entities_from_annotation_count_by_metadata($entity_type = "", $entity_subtype = "", +$name = "", $mdname = '', $mdvalue = '', $limit = 10, $owner_guid = 0, $group_guid = 0, +$asc = false, $fullview = true, $listtypetoggle = false, $pagination = true, $orderdir = 'desc') { + + $msg = 'list_entities_from_annotation_count_by_metadata() is deprecated by elgg_list_entities_from_annotation_calculation().'; + + elgg_deprecated_notice($msg, 1.8); + + $options = array(); + + $options['calculation'] = 'sum'; + + if ($entity_type) { + $options['types'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtypes'] = $entity_subtype; + } + + $options['annotation_names'] = $name; + + if ($mdname) { + $options['metadata_name'] = $mdname; + } + + if ($mdvalue) { + $options['metadata_value'] = $mdvalue; + } + + if ($owner_guid) { + if (is_array($owner_guid)) { + $options['owner_guids'] = $owner_guid; + } else { + $options['owner_guid'] = $owner_guid; + } + } + + $options['full_view'] = $fullview; + + $options['list_type_toggle'] = $listtypetoggle; + + $options['pagination'] = $pagination; + + $options['limit'] = $limit; + + $options['order_by'] = "annotation_calculation $orderdir"; + + return elgg_get_entities_from_annotation_calculation($options); +} + +/** + * Set an alternative base location for a view (as opposed to the default of $CONFIG->viewpath) + * + * @param string $view The name of the view + * @param string $location The base location path + * + * @deprecated 1.8 Use elgg_set_view_location() + */ +function set_view_location($view, $location, $viewtype = '') { + elgg_deprecated_notice("set_view_location() was deprecated by elgg_set_view_location()", 1.8); + return elgg_set_view_location($view, $location, $viewtype); +} + +/** + * Sets the URL handler for a particular entity type and subtype + * + * @param string $function_name The function to register + * @param string $entity_type The entity type + * @param string $entity_subtype The entity subtype + * @return true|false Depending on success + * + * @deprecated 1.8 Use elgg_register_entity_url_handler() + */ +function register_entity_url_handler($function_name, $entity_type = "all", $entity_subtype = "all") { + elgg_deprecated_notice("register_entity_url_handler() was deprecated by elgg_register_entity_url_handler()", 1.8); + return elgg_register_entity_url_handler($entity_type, $entity_subtype, $function_name); +} + + +/** + * Get the metadata where the entities they are referring to match a given criteria. + * + * @param mixed $meta_name Metadata name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site GUID. 0 for current, -1 for any + * + * @return mixed + * @deprecated 1.8 Use elgg_get_metadata() + */ +function find_metadata($meta_name = "", $meta_value = "", $entity_type = "", $entity_subtype = "", + $limit = 10, $offset = 0, $order_by = "", $site_guid = 0) { + + elgg_deprecated_notice('get_metadata() is deprecated by elgg_get_metadata()', 1.8); + + $options = array(); + + if ($meta_name) { + $options['annotation_name'] = $meta_name; + } + + if ($meta_value) { + $options['annotation_value'] = $meta_value; + } + + if ($entity_type) { + $options['type'] = $entity_type; + } + + if ($entity_subtype) { + $options['subtype'] = $entity_subtype; + } + + $options['limit'] = $limit; + $options['offset'] = $offset; + + if ($order_by == 'desc') { + $options['order_by'] = 'n_table.time_created desc'; + } + + if ($site_guid) { + $options['site_guid'] = $site_guid; + } + + return elgg_get_metadata($options); +} + +/** + * Get metadata objects by name. + * + * @param int $entity_guid Entity GUID + * @param string $meta_name Metadata name + * + * @return mixed ElggMetadata object, an array of ElggMetadata or false. + * @deprecated 1.8 Use elgg_get_metadata() + */ +function get_metadata_byname($entity_guid, $meta_name) { + elgg_deprecated_notice('get_metadata_byname() is deprecated by elgg_get_metadata()', 1.8); + + if (!$entity_guid || !$meta_name) { + return false; + } + + $options = array( + 'guid' => $entity_guid, + 'metadata_name' => $meta_name, + 'limit' => 0 + ); + + $md = elgg_get_metadata($options); + + if ($md && count($md) == 1) { + return $md[0]; + } + + return $md; +} + +/** + * Return all the metadata for a given GUID. + * + * @param int $entity_guid Entity GUID + * + * @return mixed + * @deprecated 1.8 Use elgg_get_metadata() + */ +function get_metadata_for_entity($entity_guid) { + elgg_deprecated_notice('get_metadata_for_entity() is deprecated by elgg_get_metadata()', 1.8); + + if (!$entity_guid) { + return false; + } + + $options = array( + 'guid' => $entity_guid, + 'limit' => 0 + ); + + return elgg_get_metadata($options); +} + +/** + * Get a specific metadata object. + * + * @param int $id The id of the metadata being retrieved. + * + * @return mixed False on failure or ElggMetadata + * @deprecated 1.8 Use elgg_get_metadata_from_id() + */ +function get_metadata($id) { + elgg_deprecated_notice('get_metadata() is deprecated by elgg_get_metadata_from_id()', 1.8); + return elgg_get_metadata_from_id($id); +} + +/** + * Clear all the metadata for a given entity, assuming you have access to that entity. + * + * @param int $guid Entity GUID + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function clear_metadata($guid) { + elgg_deprecated_notice('clear_metadata() is deprecated by elgg_delete_metadata()', 1.8); + if (!$guid) { + return false; + } + return elgg_delete_metadata(array('guid' => $guid, 'limit' => 0)); +} + +/** + * Clear all metadata belonging to a given owner_guid + * + * @param int $owner_guid The owner + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function clear_metadata_by_owner($owner_guid) { + elgg_deprecated_notice('clear_metadata() is deprecated by elgg_delete_metadata()', 1.8); + if (!$owner_guid) { + return false; + } + return elgg_delete_metadata(array('metadata_owner_guid' => $owner_guid, 'limit' => 0)); +} + +/** + * Delete a piece of metadata, where the current user has access. + * + * @param int $id The id of metadata to delete. + * + * @return bool + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function delete_metadata($id) { + elgg_deprecated_notice('delete_metadata() is deprecated by elgg_delete_metadata()', 1.8); + if (!$id) { + return false; + } + return elgg_delete_metadata(array('metadata_id' => $id)); +} + +/** + * Removes metadata on an entity with a particular name, optionally with a given value. + * + * @param int $guid The entity GUID + * @param string $name The name of the metadata + * @param string $value The value of the metadata (useful to remove a single item of a set) + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_delete_metadata() + */ +function remove_metadata($guid, $name, $value = "") { + elgg_deprecated_notice('delete_metadata() is deprecated by elgg_delete_metadata()', 1.8); + + // prevent them from deleting everything + if (!$guid) { + return false; + } + + $options = array( + 'guid' => $guid, + 'metadata_name' => $name, + 'limit' => 0 + ); + + if ($value) { + $options['metadata_value'] = $value; + } + + return elgg_delete_metadata($options); +} + +/** + * Get a specific annotation. + * + * @param int $annotation_id Annotation ID + * + * @return ElggAnnotation + * @deprecated 1.8 Use elgg_get_annotation_from_id() + */ +function get_annotation($annotation_id) { + elgg_deprecated_notice('get_annotation() is deprecated by elgg_get_annotation_from_id()', 1.8); + return elgg_get_annotation_from_id($annotation_id); +} + +/** + * Delete a given annotation. + * + * @param int $id The annotation id + * + * @return bool + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function delete_annotation($id) { + elgg_deprecated_notice('delete_annotation() is deprecated by elgg_delete_annotations()', 1.8); + if (!$id) { + return false; + } + return elgg_delete_annotations(array('annotation_id' => $annotation_id)); +} + +/** + * Clear all the annotations for a given entity, assuming you have access to that metadata. + * + * @param int $guid The entity guid + * @param string $name The name of the annotation to delete. + * + * @return int Number of annotations deleted or false if an error + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function clear_annotations($guid, $name = "") { + elgg_deprecated_notice('clear_annotations() is deprecated by elgg_delete_annotations()', 1.8); + + if (!$guid) { + return false; + } + + $options = array( + 'guid' => $guid, + 'limit' => 0 + ); + + if ($name) { + $options['annotation_name'] = $name; + } + + return elgg_delete_annotations($options); +} + +/** + * Clear all annotations belonging to a given owner_guid + * + * @param int $owner_guid The owner + * + * @return int Number of annotations deleted + * @deprecated 1.8 Use elgg_delete_annotations() + */ +function clear_annotations_by_owner($owner_guid) { + elgg_deprecated_notice('clear_annotations_by_owner() is deprecated by elgg_delete_annotations()', 1.8); + + if (!$owner_guid) { + return false; + } + + $options = array( + 'annotation_owner_guid' => $guid, + 'limit' => 0 + ); + + return elgg_delete_annotations($options); +} + +/** + * Registers a page handler for a particular identifier + * + * For example, you can register a function called 'blog_page_handler' for handler type 'blog' + * Now for all URLs of type http://yoururl/pg/blog/*, the blog_page_handler() function will be called. + * The part of the URL marked with * above will be exploded on '/' characters and passed as an + * array to that function. + * For example, the URL http://yoururl/blog/username/friends/ would result in the call: + * blog_page_handler(array('username','friends'), blog); + * + * Page handler functions should return true or the default page handler will be called. + * + * A request to register a page handler with the same identifier as previously registered + * handler will replace the previous one. + * + * The context is set to the page handler identifier before the registered + * page handler function is called. For the above example, the context is set to 'blog'. + * + * @param string $handler The page type to handle + * @param string $function Your function name + * @return true|false Depending on success + * + * @deprecated 1.8 Use {@link elgg_register_page_handler()} + */ +function register_page_handler($handler, $function){ + elgg_deprecated_notice("register_page_handler() was deprecated by elgg_register_page_handler()", 1.8); + return elgg_register_page_handler($handler, $function); +} + +/** + * Unregister a page handler for an identifier + * + * Note: to replace a page handler, call register_page_handler() + * + * @param string $handler The page type identifier + * @since 1.7.2 + * + * @deprecated 1.8 Use {@link elgg_unregister_page_handler()} + */ +function unregister_page_handler($handler) { + elgg_deprecated_notice("unregister_page_handler() was deprecated by elgg_unregister_page_handler()", 1.8); + return elgg_unregister_page_handler($handler); +} + +/** + * Register an annotation url handler. + * + * @param string $function_name The function. + * @param string $extender_name The name, default 'all'. + * + * @deprecated 1.8 Use {@link elgg_register_annotation_url_handler()} + */ +function register_annotation_url_handler($function, $extender_name) { + elgg_deprecated_notice("register_annotation_url_handler() was deprecated by elgg_register_annotation_url_handler()", 1.8); + return elgg_register_annotation_url_handler($extender_name, $function); +} + +/** + * Sets the URL handler for a particular extender type and name. + * It is recommended that you do not call this directly, instead use one of the wrapper functions in the + * subtype files. + * + * @param string $function_name The function to register + * @param string $extender_type Extender type + * @param string $extender_name The name of the extender + * @return true|false Depending on success + * + * @deprecated 1.8 Use {@link elgg_register_extender_url_handler()} + */ +function register_extender_url_handler($function, $type = "all", $name = "all") { + elgg_deprecated_notice("register_extender_url_handler() was deprecated by elgg_register_extender_url_handler()", 1.8); + return elgg_register_extender_url_handler($type, $name, $function); +} + +/** + * Registers and entity type and subtype to return in search and other places. + * A description in the elgg_echo languages file of the form item:type:subtype + * is also expected. + * + * @param string $type The type of entity (object, site, user, group) + * @param string $subtype The subtype to register (may be blank) + * @return true|false Depending on success + * + * @deprecated 1.8 Use {@link elgg_register_entity_type()} + */ +function register_entity_type($type, $subtype = null) { + elgg_deprecated_notice("register_entity_type() was deprecated by elgg_register_entity_type()", 1.8); + return elgg_register_entity_type($type, $subtype); +} + +/** + * Register a metadata url handler. + * + * @param string $function_name The function. + * @param string $extender_name The name, default 'all'. + * + * @deprecated 1.8 Use {@link elgg_register_metadata_url_handler()} + */ +function register_metadata_url_handler($function, $extender_name = "all") { + return elgg_register_metadata_url_handler($extender_name, $function); +} + +/** + * Sets the URL handler for a particular relationship type + * + * @param string $function_name The function to register + * @param string $relationship_type The relationship type. + * @return true|false Depending on success + * + * @deprecated 1.8 Use {@link elgg_register_relationship_url_handler()} + */ +function register_relationship_url_handler($function_name, $relationship_type = "all") { + elgg_deprecated_notice("register_relationship_url_handler() was deprecated by elgg_register_relationship_url_handler()", 1.8); + return elgg_register_relationship_url_handler($relationship_type, $function_name); +} + +/** + * Registers a view to be simply cached + * + * Views cached in this manner must take no parameters and be login agnostic - + * that is to say, they look the same no matter who is logged in (or logged out). + * + * CSS and the basic jS views are automatically cached like this. + * + * @param string $viewname View name + * + * @deprecated 1.8 Use {@link elgg_register_simplecache_view()} + */ +function elgg_view_register_simplecache($viewname) { + elgg_deprecated_notice("elgg_view_register_simplecache() was deprecated by elgg_register_simplecache_view()", 1.8); + return elgg_register_simplecache_view($viewname); +} + +/** + * Regenerates the simple cache. + * + * @param string $viewtype Optional viewtype to regenerate + * @see elgg_view_register_simplecache() + * + * @deprecated 1.8 Use {@link elgg_regenerate_simplecache()} + */ +function elgg_view_regenerate_simplecache($viewtype = NULL) { + elgg_deprecated_notice("elgg_view_regenerate_simplecache() was deprecated by elgg_regenerate_simplecache()", 1.8); + return elgg_regenerate_simplecache($viewtype); +} + +/** + * Enables the simple cache. + * + * @see elgg_view_register_simplecache() + * + * @deprecated 1.8 Use {@link elgg_enable_simplecache()} + */ +function elgg_view_enable_simplecache() { + elgg_deprecated_notice("elgg_view_enable_simplecache() was deprecated by elgg_enable_simplecache()", 1.8); + return elgg_enable_simplecache(); +} + +/** + * Disables the simple cache. + * + * @see elgg_view_register_simplecache() + * + * @deprecated 1.8 Use {@link elgg_disable_simplecache()} + */ +function elgg_view_disable_simplecache() { + elgg_deprecated_notice("elgg_view_disable_simplecache() was deprecated by elgg_disable_simplecache()", 1.8); + return elgg_disable_simplecache(); +} + +// these were internal functions that perhaps can be removed rather than deprecated +/** + * @deprecated 1.8 + */ +function is_db_installed() { + elgg_deprecated_notice('is_db_installed() has been deprecated', 1.8); + return true; +} + +/** + * @deprecated 1.8 + */ +function is_installed() { + elgg_deprecated_notice('is_installed() has been deprecated', 1.8); + return true; +} + +/** + * Attempt to authenticate. + * This function will process all registered PAM handlers or stop when the first + * handler fails. A handler fails by either returning false or throwing an + * exception. The advantage of throwing an exception is that it returns a message + * through the global $_PAM_HANDLERS_MSG which can be used in communication with + * a user. The order that handlers are processed is determined by the order that + * they were registered. + * + * If $credentials are provided the PAM handler should authenticate using the + * provided credentials, if not then credentials should be prompted for or + * otherwise retrieved (eg from the HTTP header or $_SESSION). + * + * @param mixed $credentials Mixed PAM handler specific credentials (e.g. username, password) + * @param string $policy - the policy type, default is "user" + * @return bool true if authenticated, false if not. + * + * @deprecated 1.8 See {@link ElggPAM} + */ +function pam_authenticate($credentials = NULL, $policy = "user") { + elgg_deprecated_notice('pam_authenticate has been deprecated for ElggPAM', 1.8); + global $_PAM_HANDLERS, $_PAM_HANDLERS_MSG; + + $_PAM_HANDLERS_MSG = array(); + + $authenticated = false; + + foreach ($_PAM_HANDLERS[$policy] as $k => $v) { + $handler = $v->handler; + $importance = $v->importance; + + try { + // Execute the handler + if ($handler($credentials)) { + // Explicitly returned true + $_PAM_HANDLERS_MSG[$k] = "Authenticated!"; + + $authenticated = true; + } else { + $_PAM_HANDLERS_MSG[$k] = "Not Authenticated."; + + // If this is required then abort. + if ($importance == 'required') { + return false; + } + } + } catch (Exception $e) { + $_PAM_HANDLERS_MSG[$k] = "$e"; + + // If this is required then abort. + if ($importance == 'required') { + return false; + } + } + } + + return $authenticated; +} + + +/** + * When given a widget entity and a new requested location, saves the new location + * and also provides a sensible ordering for all widgets in that column + * + * @param ElggObject $widget The widget entity + * @param int $order The order within the column + * @param int $column The column (1, 2 or 3) + * + * @return bool Depending on success + * @deprecated 1.8 use ElggWidget::move() + */ +function save_widget_location(ElggObject $widget, $order, $column) { + elgg_deprecated_notice('save_widget_location() is deprecated', 1.8); + if ($widget instanceof ElggObject) { + if ($widget->subtype == "widget") { + // If you can't move the widget, don't save a new location + if (!$widget->draggable) { + return false; + } + + // Sanitise the column value + if ($column != 1 || $column != 2 || $column != 3) { + $column = 1; + } + + $widget->column = (int) $column; + + $ordertmp = array(); + $params = array( + 'context' => $widget->context, + 'column' => $column, + ); + + if ($entities = get_entities_from_metadata_multi($params, 'object', 'widget')) { + foreach ($entities as $entity) { + $entityorder = $entity->order; + if ($entityorder < $order) { + $ordertmp[$entityorder] = $entity; + } + if ($entityorder >= $order) { + $ordertmp[$entityorder + 10000] = $entity; + } + } + } + + $ordertmp[$order] = $widget; + ksort($ordertmp); + + $orderticker = 10; + foreach ($ordertmp as $orderval => $entity) { + $entity->order = $orderticker; + $orderticker += 10; + } + + return true; + } else { + register_error($widget->subtype); + } + + } + + return false; +} + +/** + * Get widgets for a particular context and column, in order of display + * + * @param int $user_guid The owner user GUID + * @param string $context The context (profile, dashboard etc) + * @param int $column The column (1 or 2) + * + * @return array|false An array of widget ElggObjects, or false + * @deprecated 1.8 Use elgg_get_widgets() + */ +function get_widgets($user_guid, $context, $column) { + elgg_deprecated_notice('get_widgets is depecated for elgg_get_widgets', 1.8); + $params = array( + 'column' => $column, + 'context' => $context + ); + $widgets = get_entities_from_private_setting_multi($params, "object", + "widget", $user_guid, "", 10000); + + if ($widgets) { + $widgetorder = array(); + foreach ($widgets as $widget) { + $order = $widget->order; + while (isset($widgetorder[$order])) { + $order++; + } + $widgetorder[$order] = $widget; + } + + ksort($widgetorder); + + return $widgetorder; + } + + return false; +} + +/** + * Add a new widget instance + * + * @param int $entity_guid GUID of entity that owns this widget + * @param string $handler The handler for this widget + * @param string $context The page context for this widget + * @param int $order The order to display this widget in + * @param int $column The column to display this widget in (1, 2 or 3) + * @param int $access_id If not specified, it is set to the default access level + * + * @return int|false Widget GUID or false on failure + * @deprecated 1.8 use elgg_create_widget() + */ +function add_widget($entity_guid, $handler, $context, $order = 0, $column = 1, $access_id = null) { + elgg_deprecated_notice('add_widget has been deprecated for elgg_create_widget', 1.8); + if (empty($entity_guid) || empty($context) || empty($handler) || !widget_type_exists($handler)) { + return false; + } + + if ($entity = get_entity($entity_guid)) { + $widget = new ElggWidget; + $widget->owner_guid = $entity_guid; + $widget->container_guid = $entity_guid; + if (isset($access_id)) { + $widget->access_id = $access_id; + } else { + $widget->access_id = get_default_access(); + } + + $guid = $widget->save(); + + // private settings cannot be set until ElggWidget saved + $widget->handler = $handler; + $widget->context = $context; + $widget->column = $column; + $widget->order = $order; + + return $guid; + } + + return false; +} + +/** + * Define a new widget type + * + * @param string $handler The identifier for the widget handler + * @param string $name The name of the widget type + * @param string $description A description for the widget type + * @param string $context A comma-separated list of contexts where this + * widget is allowed (default: 'all') + * @param bool $multiple Whether or not multiple instances of this widget + * are allowed on a single dashboard (default: false) + * @param string $positions A comma-separated list of positions on the page + * (side or main) where this widget is allowed (default: "side,main") + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_register_widget_type + */ +function add_widget_type($handler, $name, $description, $context = "all", +$multiple = false, $positions = "side,main") { + elgg_deprecated_notice("add_widget_type deprecated for elgg_register_widget_type", 1.8); + + return elgg_register_widget_type($handler, $name, $description, $context, $multiple); +} + +/** + * Remove a widget type + * + * @param string $handler The identifier for the widget handler + * + * @return void + * @since 1.7.1 + * @deprecated 1.8 Use elgg_unregister_widget_type + */ +function remove_widget_type($handler) { + elgg_deprecated_notice("remove_widget_type deprecated for elgg_unregister_widget_type", 1.8); + return elgg_unregister_widget_type($handler); +} + +/** + * Determines whether or not widgets with the specified handler have been defined + * + * @param string $handler The widget handler identifying string + * + * @return bool Whether or not those widgets exist + * @deprecated 1.8 Use elgg_is_widget_type + */ +function widget_type_exists($handler) { + elgg_deprecated_notice("widget_type_exists deprecated for elgg_is_widget_type", 1.8); + return elgg_is_widget_type($handler); +} + +/** + * Returns an array of stdClass objects representing the defined widget types + * + * @return array A list of types defined (if any) + * @deprecated 1.8 Use elgg_get_widget_types + */ +function get_widget_types() { + elgg_deprecated_notice("get_widget_types deprecrated for elgg_get_widget_types", 1.8); + return elgg_get_widget_types(); +} + +/** + * Saves a widget's settings (by passing an array of + * (name => value) pairs to save_{$handler}_widget) + * + * @param int $widget_guid The GUID of the widget we're saving to + * @param array $params An array of name => value parameters + * + * @return bool + * @deprecated 1.8 Use elgg_save_widget_settings + */ +function save_widget_info($widget_guid, $params) { + elgg_deprecated_notice("save_widget_info() is deprecated for elgg_save_widget_settings", 1.8); + if ($widget = get_entity($widget_guid)) { + + $subtype = $widget->getSubtype(); + + if ($subtype != "widget") { + return false; + } + $handler = $widget->handler; + if (empty($handler) || !widget_type_exists($handler)) { + return false; + } + + if (!$widget->canEdit()) { + return false; + } + + // Save the params to the widget + if (is_array($params) && sizeof($params) > 0) { + foreach ($params as $name => $value) { + + if (!empty($name) && !in_array($name, array( + 'guid', 'owner_guid', 'site_guid' + ))) { + if (is_array($value)) { + // @todo Handle arrays securely + $widget->setMetaData($name, $value, "", true); + } else { + $widget->$name = $value; + } + } + } + $widget->save(); + } + + $function = "save_{$handler}_widget"; + if (is_callable($function)) { + return $function($params); + } + + return true; + } + + return false; +} + +/** + * Reorders the widgets from a widget panel + * + * @param string $panelstring1 String of guids of ElggWidget objects separated by :: + * @param string $panelstring2 String of guids of ElggWidget objects separated by :: + * @param string $panelstring3 String of guids of ElggWidget objects separated by :: + * @param string $context Profile or dashboard + * @param int $owner Owner guid + * + * @return void + * @deprecated 1.8 Don't use. + */ +function reorder_widgets_from_panel($panelstring1, $panelstring2, $panelstring3, $context, $owner) { + elgg_deprecated_notice("reorder_widgets_from_panel() is deprecated", 1.8); + $return = true; + + $mainwidgets = explode('::', $panelstring1); + $sidewidgets = explode('::', $panelstring2); + $rightwidgets = explode('::', $panelstring3); + + $handlers = array(); + $guids = array(); + + if (is_array($mainwidgets) && sizeof($mainwidgets) > 0) { + foreach ($mainwidgets as $widget) { + + $guid = (int) $widget; + + if ("{$guid}" == "{$widget}") { + $guids[1][] = $widget; + } else { + $handlers[1][] = $widget; + } + } + } + if (is_array($sidewidgets) && sizeof($sidewidgets) > 0) { + foreach ($sidewidgets as $widget) { + + $guid = (int) $widget; + + if ("{$guid}" == "{$widget}") { + $guids[2][] = $widget; + } else { + $handlers[2][] = $widget; + } + + } + } + if (is_array($rightwidgets) && sizeof($rightwidgets) > 0) { + foreach ($rightwidgets as $widget) { + + $guid = (int) $widget; + + if ("{$guid}" == "{$widget}") { + $guids[3][] = $widget; + } else { + $handlers[3][] = $widget; + } + + } + } + + // Reorder existing widgets or delete ones that have vanished + foreach (array(1, 2, 3) as $column) { + if ($dbwidgets = get_widgets($owner, $context, $column)) { + + foreach ($dbwidgets as $dbwidget) { + if (in_array($dbwidget->getGUID(), $guids[1]) + || in_array($dbwidget->getGUID(), $guids[2]) || in_array($dbwidget->getGUID(), $guids[3])) { + + if (in_array($dbwidget->getGUID(), $guids[1])) { + $pos = array_search($dbwidget->getGUID(), $guids[1]); + $col = 1; + } else if (in_array($dbwidget->getGUID(), $guids[2])) { + $pos = array_search($dbwidget->getGUID(), $guids[2]); + $col = 2; + } else { + $pos = array_search($dbwidget->getGUID(), $guids[3]); + $col = 3; + } + $pos = ($pos + 1) * 10; + $dbwidget->column = $col; + $dbwidget->order = $pos; + } else { + $dbguid = $dbwidget->getGUID(); + if (!$dbwidget->delete()) { + $return = false; + } else { + // Remove state cookie + setcookie('widget' + $dbguid, null); + } + } + } + + } + // Add new ones + if (sizeof($guids[$column]) > 0) { + foreach ($guids[$column] as $key => $guid) { + if ($guid == 0) { + $pos = ($key + 1) * 10; + $handler = $handlers[$column][$key]; + if (!add_widget($owner, $handler, $context, $pos, $column)) { + $return = false; + } + } + } + } + } + + return $return; +} + +/** + * Register a particular context for use with widgets. + * + * @param string $context The context we wish to enable context for + * + * @return void + * @deprecated 1.8 Don't use. + */ +function use_widgets($context) { + elgg_deprecated_notice("use_widgets is deprecated", 1.8); + global $CONFIG; + + if (!isset($CONFIG->widgets)) { + $CONFIG->widgets = new stdClass; + } + + if (!isset($CONFIG->widgets->contexts)) { + $CONFIG->widgets->contexts = array(); + } + + if (!empty($context)) { + $CONFIG->widgets->contexts[] = $context; + } +} + +/** + * Determines whether or not the current context is using widgets + * + * @return bool Depending on widget status + * @deprecated 1.8 Don't use. + */ +function using_widgets() { + elgg_deprecated_notice("using_widgets is deprecated", 1.8); + global $CONFIG; + + $context = elgg_get_context(); + if (isset($CONFIG->widgets->contexts) && is_array($CONFIG->widgets->contexts)) { + if (in_array($context, $CONFIG->widgets->contexts)) { + return true; + } + } + + return false; +} + +/** + * Displays a particular widget + * + * @param ElggObject $widget The widget to display + * @return string The HTML for the widget, including JavaScript wrapper + * + * @deprecated 1.8 Use elgg_view_entity() + */ +function display_widget(ElggObject $widget) { + elgg_deprecated_notice("display_widget() was been deprecated. Use elgg_view_entity().", 1.8); + return elgg_view_entity($widget); +} + +/** + * Count the number of comments attached to an entity + * + * @param ElggEntity $entity + * @return int Number of comments + * @deprecated 1.8 Use ElggEntity->countComments() + */ +function elgg_count_comments($entity) { + elgg_deprecated_notice('elgg_count_comments() is deprecated by ElggEntity->countComments()', 1.8); + + if ($entity instanceof ElggEntity) { + return $entity->countComments(); + } + + return 0; +} + +/** + * Removes all items relating to a particular acting entity from the river + * + * @param int $subject_guid The GUID of the entity + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_subject($subject_guid) { + elgg_deprecated_notice("remove_from_river_by_subject() deprecated by elgg_delete_river()", 1.8); + + return elgg_delete_river(array('subject_guid' => $subject_guid)); +} + +/** + * Removes all items relating to a particular entity being acted upon from the river + * + * @param int $object_guid The GUID of the entity + * + * @return bool Depending on success + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_object($object_guid) { + elgg_deprecated_notice("remove_from_river_by_object() deprecated by elgg_delete_river()", 1.8); + + return elgg_delete_river(array('object_guid' => $object_guid)); +} + +/** + * Removes all items relating to a particular annotation being acted upon from the river + * + * @param int $annotation_id The ID of the annotation + * + * @return bool Depending on success + * @since 1.7.0 + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_annotation($annotation_id) { + elgg_deprecated_notice("remove_from_river_by_annotation() deprecated by elgg_delete_river()", 1.8); + + return elgg_delete_river(array('annotation_id' => $annotation_id)); +} + +/** + * Removes a single river entry + * + * @param int $id The ID of the river entry + * + * @return bool Depending on success + * @since 1.7.2 + * @deprecated 1.8 Use elgg_delete_river() + */ +function remove_from_river_by_id($id) { + elgg_deprecated_notice("remove_from_river_by_id() deprecated by elgg_delete_river()", 1.8); + + return elgg_delete_river(array('id' => $id)); +} + +/** + * A default page handler + * Tries to locate a suitable file to include. Only works for core pages, not plugins. + * + * @param array $page The page URL elements + * @param string $handler The base handler + * + * @return true|false Depending on success + * @deprecated 1.8 + */ +function default_page_handler($page, $handler) { + global $CONFIG; + + elgg_deprecated_notice("default_page_handler is deprecated", "1.8"); + + $page = implode('/', $page); + + // protect against including arbitary files + $page = str_replace("..", "", $page); + + $callpath = $CONFIG->path . $handler . "/" . $page; + if (is_dir($callpath)) { + $callpath = sanitise_filepath($callpath); + $callpath .= "index.php"; + if (file_exists($callpath)) { + if (include($callpath)) { + return TRUE; + } + } + } else if (file_exists($callpath)) { + include($callpath); + return TRUE; + } + + return FALSE; +} + +/** + * Invalidate this class's entry in the cache. + * + * @param int $guid The entity guid + * + * @return void + * @access private + * @deprecated 1.8 + */ +function invalidate_cache_for_entity($guid) { + elgg_deprecated_notice('invalidate_cache_for_entity() is a private function and should not be used.', 1.8); + _elgg_invalidate_cache_for_entity($guid); +} + +/** + * Cache an entity. + * + * Stores an entity in $ENTITY_CACHE; + * + * @param ElggEntity $entity Entity to cache + * + * @return void + * @access private + * @deprecated 1.8 + */ +function cache_entity(ElggEntity $entity) { + elgg_deprecated_notice('cache_entity() is a private function and should not be used.', 1.8); + _elgg_cache_entity($entity); +} + +/** + * Retrieve a entity from the cache. + * + * @param int $guid The guid + * + * @return ElggEntity|bool false if entity not cached, or not fully loaded + * @access private + * @deprecated 1.8 + */ +function retrieve_cached_entity($guid) { + elgg_deprecated_notice('retrieve_cached_entity() is a private function and should not be used.', 1.8); + return _elgg_retrieve_cached_entity($guid); +} diff --git a/engine/lib/deprecated-1.9.php b/engine/lib/deprecated-1.9.php new file mode 100644 index 000000000..31d03428f --- /dev/null +++ b/engine/lib/deprecated-1.9.php @@ -0,0 +1,582 @@ +<?php +/** + * Return a timestamp for the start of a given day (defaults today). + * + * @param int $day Day + * @param int $month Month + * @param int $year Year + * + * @return int + * @access private + * @deprecated 1.9 + */ +function get_day_start($day = null, $month = null, $year = null) { + elgg_deprecated_notice('get_day_start() has been deprecated', 1.9); + return mktime(0, 0, 0, $month, $day, $year); +} + +/** + * Return a timestamp for the end of a given day (defaults today). + * + * @param int $day Day + * @param int $month Month + * @param int $year Year + * + * @return int + * @access private + * @deprecated 1.9 + */ +function get_day_end($day = null, $month = null, $year = null) { + elgg_deprecated_notice('get_day_end() has been deprecated', 1.9); + return mktime(23, 59, 59, $month, $day, $year); +} + +/** + * Return the notable entities for a given time period. + * + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param string $order_by The field to order by; by default, time_created desc + * @param int $limit The number of entities to return; 10 by default + * @param int $offset The indexing offset, 0 by default + * @param boolean $count Set to true to get a count instead of entities. Defaults to false. + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any. + * @param mixed $container_guid Container or containers to get entities from (default: any). + * + * @return array|false + * @access private + * @deprecated 1.9 + */ +function get_notable_entities($start_time, $end_time, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "asc", $limit = 10, $offset = 0, $count = false, $site_guid = 0, +$container_guid = null) { + elgg_deprecated_notice('get_notable_entities() has been deprecated', 1.9); + global $CONFIG; + + if ($subtype === false || $subtype === null || $subtype === 0) { + return false; + } + + $start_time = (int)$start_time; + $end_time = (int)$end_time; + $order_by = sanitise_string($order_by); + $limit = (int)$limit; + $offset = (int)$offset; + $site_guid = (int) $site_guid; + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + $where = array(); + + if (is_array($type)) { + $tempwhere = ""; + if (sizeof($type)) { + foreach ($type as $typekey => $subtypearray) { + foreach ($subtypearray as $subtypeval) { + $typekey = sanitise_string($typekey); + if (!empty($subtypeval)) { + $subtypeval = (int) get_subtype_id($typekey, $subtypeval); + } else { + $subtypeval = 0; + } + if (!empty($tempwhere)) { + $tempwhere .= " or "; + } + $tempwhere .= "(e.type = '{$typekey}' and e.subtype = {$subtypeval})"; + } + } + } + if (!empty($tempwhere)) { + $where[] = "({$tempwhere})"; + } + } else { + $type = sanitise_string($type); + $subtype = get_subtype_id($type, $subtype); + + if ($type != "") { + $where[] = "e.type='$type'"; + } + + if ($subtype !== "") { + $where[] = "e.subtype=$subtype"; + } + } + + if ($owner_guid != "") { + if (!is_array($owner_guid)) { + $owner_array = array($owner_guid); + $owner_guid = (int) $owner_guid; + $where[] = "e.owner_guid = '$owner_guid'"; + } else if (sizeof($owner_guid) > 0) { + $owner_array = array_map('sanitise_int', $owner_guid); + // Cast every element to the owner_guid array to int + $owner_guid = implode(",", $owner_guid); + $where[] = "e.owner_guid in ({$owner_guid})"; + } + if (is_null($container_guid)) { + $container_guid = $owner_array; + } + } + + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + + if (!is_null($container_guid)) { + if (is_array($container_guid)) { + foreach ($container_guid as $key => $val) { + $container_guid[$key] = (int) $val; + } + $where[] = "e.container_guid in (" . implode(",", $container_guid) . ")"; + } else { + $container_guid = (int) $container_guid; + $where[] = "e.container_guid = {$container_guid}"; + } + } + + // Add the calendar stuff + $cal_join = " + JOIN {$CONFIG->dbprefix}metadata cal_start on e.guid=cal_start.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_start_name on cal_start.name_id=cal_start_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_start_value on cal_start.value_id=cal_start_value.id + + JOIN {$CONFIG->dbprefix}metadata cal_end on e.guid=cal_end.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_end_name on cal_end.name_id=cal_end_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_end_value on cal_end.value_id=cal_end_value.id + "; + $where[] = "cal_start_name.string='calendar_start'"; + $where[] = "cal_start_value.string>=$start_time"; + $where[] = "cal_end_name.string='calendar_end'"; + $where[] = "cal_end_value.string <= $end_time"; + + + if (!$count) { + $query = "SELECT e.* from {$CONFIG->dbprefix}entities e $cal_join where "; + } else { + $query = "SELECT count(e.guid) as total from {$CONFIG->dbprefix}entities e $cal_join where "; + } + foreach ($where as $w) { + $query .= " $w and "; + } + + $query .= get_access_sql_suffix('e'); // Add access controls + + if (!$count) { + $query .= " order by n.calendar_start $order_by"; + // Add order and limit + if ($limit) { + $query .= " limit $offset, $limit"; + } + $dt = get_data($query, "entity_row_to_elggstar"); + + return $dt; + } else { + $total = get_data_row($query); + return $total->total; + } +} + +/** + * Return the notable entities for a given time period based on an item of metadata. + * + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @param mixed $meta_name Metadata name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any. + * @param bool $count If true, returns count instead of entities. (Default: false) + * + * @return int|array A list of entities, or a count if $count is set to true + * @access private + * @deprecated 1.9 + */ +function get_notable_entities_from_metadata($start_time, $end_time, $meta_name, $meta_value = "", +$entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", +$site_guid = 0, $count = false) { + elgg_deprecated_notice('get_notable_entities_from_metadata() has been deprecated', 1.9); + + global $CONFIG; + + $meta_n = get_metastring_id($meta_name); + $meta_v = get_metastring_id($meta_value); + + $start_time = (int)$start_time; + $end_time = (int)$end_time; + $entity_type = sanitise_string($entity_type); + $entity_subtype = get_subtype_id($entity_type, $entity_subtype); + $limit = (int)$limit; + $offset = (int)$offset; + if ($order_by == "") { + $order_by = "e.time_created desc"; + } + $order_by = sanitise_string($order_by); + $site_guid = (int) $site_guid; + if ((is_array($owner_guid) && (count($owner_guid)))) { + foreach ($owner_guid as $key => $guid) { + $owner_guid[$key] = (int) $guid; + } + } else { + $owner_guid = (int) $owner_guid; + } + + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + //$access = get_access_list(); + + $where = array(); + + if ($entity_type != "") { + $where[] = "e.type='$entity_type'"; + } + + if ($entity_subtype) { + $where[] = "e.subtype=$entity_subtype"; + } + + if ($meta_name != "") { + $where[] = "m.name_id='$meta_n'"; + } + + if ($meta_value != "") { + $where[] = "m.value_id='$meta_v'"; + } + + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + + if (is_array($owner_guid)) { + $where[] = "e.container_guid in (" . implode(",", $owner_guid) . ")"; + } else if ($owner_guid > 0) { + $where[] = "e.container_guid = {$owner_guid}"; + } + + // Add the calendar stuff + $cal_join = " + JOIN {$CONFIG->dbprefix}metadata cal_start on e.guid=cal_start.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_start_name on cal_start.name_id=cal_start_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_start_value on cal_start.value_id=cal_start_value.id + + JOIN {$CONFIG->dbprefix}metadata cal_end on e.guid=cal_end.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_end_name on cal_end.name_id=cal_end_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_end_value on cal_end.value_id=cal_end_value.id + "; + + $where[] = "cal_start_name.string='calendar_start'"; + $where[] = "cal_start_value.string>=$start_time"; + $where[] = "cal_end_name.string='calendar_end'"; + $where[] = "cal_end_value.string <= $end_time"; + + if (!$count) { + $query = "SELECT distinct e.* "; + } else { + $query = "SELECT count(distinct e.guid) as total "; + } + + $query .= "from {$CONFIG->dbprefix}entities e" + . " JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid $cal_join where"; + + foreach ($where as $w) { + $query .= " $w and "; + } + + // Add access controls + $query .= get_access_sql_suffix("e"); + $query .= ' and ' . get_access_sql_suffix("m"); + + if (!$count) { + // Add order and limit + $query .= " order by $order_by limit $offset, $limit"; + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($row = get_data_row($query)) { + return $row->total; + } + } + + return false; +} + +/** + * Return the notable entities for a given time period based on their relationship. + * + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Reverse the normal function of the query to say + * "give me all entities for whom $relationship_guid is a + * $relationship of" + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Owner GUID + * @param string $order_by Optional Order by + * @param int $limit Limit + * @param int $offset Offset + * @param boolean $count If true returns a count of entities (default false) + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + * @access private + * @deprecated 1.9 + */ +function get_noteable_entities_from_relationship($start_time, $end_time, $relationship, +$relationship_guid, $inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + elgg_deprecated_notice('get_noteable_entities_from_relationship() has been deprecated', 1.9); + + global $CONFIG; + + $start_time = (int)$start_time; + $end_time = (int)$end_time; + $relationship = sanitise_string($relationship); + $relationship_guid = (int)$relationship_guid; + $inverse_relationship = (bool)$inverse_relationship; + $type = sanitise_string($type); + $subtype = get_subtype_id($type, $subtype); + $owner_guid = (int)$owner_guid; + if ($order_by == "") { + $order_by = "time_created desc"; + } + $order_by = sanitise_string($order_by); + $limit = (int)$limit; + $offset = (int)$offset; + $site_guid = (int) $site_guid; + if ($site_guid == 0) { + $site_guid = $CONFIG->site_guid; + } + + //$access = get_access_list(); + + $where = array(); + + if ($relationship != "") { + $where[] = "r.relationship='$relationship'"; + } + if ($relationship_guid) { + $where[] = $inverse_relationship ? + "r.guid_two='$relationship_guid'" : "r.guid_one='$relationship_guid'"; + } + if ($type != "") { + $where[] = "e.type='$type'"; + } + if ($subtype) { + $where[] = "e.subtype=$subtype"; + } + if ($owner_guid != "") { + $where[] = "e.container_guid='$owner_guid'"; + } + if ($site_guid > 0) { + $where[] = "e.site_guid = {$site_guid}"; + } + + // Add the calendar stuff + $cal_join = " + JOIN {$CONFIG->dbprefix}metadata cal_start on e.guid=cal_start.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_start_name on cal_start.name_id=cal_start_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_start_value on cal_start.value_id=cal_start_value.id + + JOIN {$CONFIG->dbprefix}metadata cal_end on e.guid=cal_end.entity_guid + JOIN {$CONFIG->dbprefix}metastrings cal_end_name on cal_end.name_id=cal_end_name.id + JOIN {$CONFIG->dbprefix}metastrings cal_end_value on cal_end.value_id=cal_end_value.id + "; + $where[] = "cal_start_name.string='calendar_start'"; + $where[] = "cal_start_value.string>=$start_time"; + $where[] = "cal_end_name.string='calendar_end'"; + $where[] = "cal_end_value.string <= $end_time"; + + // Select what we're joining based on the options + $joinon = "e.guid = r.guid_one"; + if (!$inverse_relationship) { + $joinon = "e.guid = r.guid_two"; + } + + if ($count) { + $query = "SELECT count(distinct e.guid) as total "; + } else { + $query = "SELECT distinct e.* "; + } + $query .= " from {$CONFIG->dbprefix}entity_relationships r" + . " JOIN {$CONFIG->dbprefix}entities e on $joinon $cal_join where "; + + foreach ($where as $w) { + $query .= " $w and "; + } + // Add access controls + $query .= get_access_sql_suffix("e"); + if (!$count) { + $query .= " order by $order_by limit $offset, $limit"; // Add order and limit + return get_data($query, "entity_row_to_elggstar"); + } else { + if ($count = get_data_row($query)) { + return $count->total; + } + } + return false; +} + +/** + * Get all entities for today. + * + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param string $order_by The field to order by; by default, time_created desc + * @param int $limit The number of entities to return; 10 by default + * @param int $offset The indexing offset, 0 by default + * @param boolean $count If true returns a count of entities (default false) + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any + * @param mixed $container_guid Container(s) to get entities from (default: any). + * + * @return array|false + * @access private + * @deprecated 1.9 + */ +function get_todays_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", +$limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid = null) { + elgg_deprecated_notice('get_todays_entities() has been deprecated', 1.9); + + $day_start = get_day_start(); + $day_end = get_day_end(); + + return get_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $order_by, + $limit, $offset, $count, $site_guid, $container_guid); +} + +/** + * Get entities for today from metadata. + * + * @param mixed $meta_name Metadata name + * @param mixed $meta_value Metadata value + * @param string $entity_type The type of entity to look for, eg 'site' or 'object' + * @param string $entity_subtype The subtype of the entity. + * @param int $owner_guid Owner GUID + * @param int $limit Limit + * @param int $offset Offset + * @param string $order_by Optional ordering. + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any. + * @param bool $count If true, returns count instead of entities. (Default: false) + * + * @return int|array A list of entities, or a count if $count is set to true + * @access private + * @deprecated 1.9 + */ +function get_todays_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", +$entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, +$count = false) { + elgg_deprecated_notice('get_todays_entities_from_metadata() has been deprecated', 1.9); + + $day_start = get_day_start(); + $day_end = get_day_end(); + + return get_notable_entities_from_metadata($day_start, $day_end, $meta_name, $meta_value, + $entity_type, $entity_subtype, $owner_guid, $limit, $offset, $order_by, $site_guid, $count); +} + +/** + * Get entities for today from a relationship + * + * @param string $relationship The relationship eg "friends_of" + * @param int $relationship_guid The guid of the entity to use query + * @param bool $inverse_relationship Reverse the normal function of the query to say + * "give me all entities for whom $relationship_guid is a + * $relationship of" + * @param string $type Entity type + * @param string $subtype Entity subtype + * @param int $owner_guid Owner GUID + * @param string $order_by Optional Order by + * @param int $limit Limit + * @param int $offset Offset + * @param boolean $count If true returns a count of entities (default false) + * @param int $site_guid Site to get entities for. Default 0 = current site. -1 = any + * + * @return array|int|false An array of entities, or the number of entities, or false on failure + * @access private + * @deprecated 1.9 + */ +function get_todays_entities_from_relationship($relationship, $relationship_guid, +$inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, +$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { + elgg_deprecated_notice('get_todays_entities_from_relationship() has been deprecated', 1.9); + + $day_start = get_day_start(); + $day_end = get_day_end(); + + return get_notable_entities_from_relationship($day_start, $day_end, $relationship, + $relationship_guid, $inverse_relationship, $type, $subtype, $owner_guid, $order_by, + $limit, $offset, $count, $site_guid); +} + +/** + * Returns a viewable list of entities for a given time period. + * + * @see elgg_view_entity_list + * + * @param int $start_time The start time as a unix timestamp. + * @param int $end_time The end time as a unix timestamp. + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $limit The number of entities to return; 10 by default + * @param boolean $fullview Whether or not to display the full view (default: true) + * @param boolean $listtypetoggle Whether or not to allow gallery view + * @param boolean $navigation Display pagination? Default: true + * + * @return string A viewable list of entities + * @access private + * @deprecated 1.9 + */ +function list_notable_entities($start_time, $end_time, $type= "", $subtype = "", $owner_guid = 0, +$limit = 10, $fullview = true, $listtypetoggle = false, $navigation = true) { + elgg_deprecated_notice('list_notable_entities() has been deprecated', 1.9); + + $offset = (int) get_input('offset'); + $count = get_notable_entities($start_time, $end_time, $type, $subtype, + $owner_guid, "", $limit, $offset, true); + + $entities = get_notable_entities($start_time, $end_time, $type, $subtype, + $owner_guid, "", $limit, $offset); + + return elgg_view_entity_list($entities, $count, $offset, $limit, + $fullview, $listtypetoggle, $navigation); +} + +/** + * Return a list of today's entities. + * + * @see list_notable_entities + * + * @param string $type The type of entity (eg "user", "object" etc) + * @param string $subtype The arbitrary subtype of the entity + * @param int $owner_guid The GUID of the owning user + * @param int $limit The number of entities to return; 10 by default + * @param boolean $fullview Whether or not to display the full view (default: true) + * @param boolean $listtypetoggle Whether or not to allow gallery view + * @param boolean $navigation Display pagination? Default: true + * + * @return string A viewable list of entities + * @access private + * @deprecated 1.9 + */ +function list_todays_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, +$fullview = true, $listtypetoggle = false, $navigation = true) { + elgg_deprecated_notice('list_todays_entities() has been deprecated', 1.9); + + $day_start = get_day_start(); + $day_end = get_day_end(); + + return list_notable_entities($day_start, $day_end, $type, $subtype, $owner_guid, $limit, + $fullview, $listtypetoggle, $navigation); +} diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php index 7dd485227..34111c69d 100644 --- a/engine/lib/elgglib.php +++ b/engine/lib/elgglib.php @@ -18,12 +18,13 @@ elgg_register_classes(dirname(dirname(__FILE__)) . '/classes'); * * @return void * @throws Exception + * @access private */ function _elgg_autoload($class) { global $CONFIG; - if (!include($CONFIG->classes[$class])) { - throw new Exception("Failed to autoload $class"); + if (!isset($CONFIG->classes[$class]) || !include($CONFIG->classes[$class])) { + return false; } } @@ -34,6 +35,7 @@ function _elgg_autoload($class) { * @param string $dir The dir to look in * * @return void + * @since 1.8.0 */ function elgg_register_classes($dir) { $classes = elgg_get_file_list($dir, array(), array(), array('.php')); @@ -49,7 +51,8 @@ function elgg_register_classes($dir) { * @param string $class The name of the class * @param string $location The location of the file * - * @return void + * @return true + * @since 1.8.0 */ function elgg_register_class($class, $location) { global $CONFIG; @@ -59,6 +62,66 @@ function elgg_register_class($class, $location) { } $CONFIG->classes[$class] = $location; + + return true; +} + +/** + * Register a php library. + * + * @param string $name The name of the library + * @param string $location The location of the file + * + * @return void + * @since 1.8.0 + */ +function elgg_register_library($name, $location) { + global $CONFIG; + + if (!isset($CONFIG->libraries)) { + $CONFIG->libraries = array(); + } + + $CONFIG->libraries[$name] = $location; +} + +/** + * Load a php library. + * + * @param string $name The name of the library + * + * @return void + * @throws InvalidParameterException + * @since 1.8.0 + * @todo return boolean in 1.9 to indicate whether the library has been loaded + */ +function elgg_load_library($name) { + global $CONFIG; + + static $loaded_libraries = array(); + + if (in_array($name, $loaded_libraries)) { + return; + } + + if (!isset($CONFIG->libraries)) { + $CONFIG->libraries = array(); + } + + if (!isset($CONFIG->libraries[$name])) { + $error = elgg_echo('InvalidParameterException:LibraryNotRegistered', array($name)); + throw new InvalidParameterException($error); + } + + if (!include_once($CONFIG->libraries[$name])) { + $error = elgg_echo('InvalidParameterException:LibraryNotFound', array( + $name, + $CONFIG->libraries[$name]) + ); + throw new InvalidParameterException($error); + } + + $loaded_libraries[] = $name; } /** @@ -68,13 +131,13 @@ function elgg_register_class($class, $location) { * already been sent, returns FALSE. * * @param string $location URL to forward to browser to. Can be path relative to the network's URL. + * @param string $reason Short explanation for why we're forwarding * - * @return False False if headers have been sent. Terminates execution if forwarding. + * @return false False if headers have been sent. Terminates execution if forwarding. + * @throws SecurityException */ -function forward($location = "") { - global $CONFIG; - - if (!headers_sent()) { +function forward($location = "", $reason = 'system') { + if (!headers_sent($file, $line)) { if ($location === REFERER) { $location = $_SERVER['HTTP_REFERER']; } @@ -84,7 +147,7 @@ function forward($location = "") { // return new forward location or false to stop the forward or empty string to exit $current_page = current_page_url(); $params = array('current_url' => $current_page, 'forward_url' => $location); - $location = trigger_plugin_hook('forward', 'system', $params, $location); + $location = elgg_trigger_plugin_hook('forward', $reason, $params, $location); if ($location) { header("Location: {$location}"); @@ -92,9 +155,9 @@ function forward($location = "") { } else if ($location === '') { exit; } + } else { + throw new SecurityException(elgg_echo('SecurityException:ForwardFailedToRedirect', array($file, $line))); } - - return false; } /** @@ -106,243 +169,283 @@ function forward($location = "") { * JavaScript from a view that may be called more than once. It also handles * more than one plugin adding the same JavaScript. * - * Plugin authors are encouraged to use the $id variable. jQuery plugins - * often have filenames such as jquery.rating.js. In that case, the id - * would be "jquery.rating". It is recommended to not use version numbers - * in the id. + * jQuery plugins often have filenames such as jquery.rating.js. A best practice + * is to base $name on the filename: "jquery.rating". It is recommended to not + * use version numbers in the name. * * The JavaScript files can be local to the server or remote (such as * Google's CDN). * + * @param string $name An identifier for the JavaScript library * @param string $url URL of the JavaScript file - * @param string $id An identifier of the JavaScript library * @param string $location Page location: head or footer. (default: head) + * @param int $priority Priority of the JS file (lower numbers load earlier) + * * @return bool + * @since 1.8.0 */ -function elgg_register_js($url, $id = '', $location = 'head') { - return elgg_register_external_file('javascript', $url, $id, $location); +function elgg_register_js($name, $url, $location = 'head', $priority = null) { + return elgg_register_external_file('js', $name, $url, $location, $priority); } /** - * Register a CSS file for inclusion in the HTML head + * Unregister a JavaScript file + * + * @param string $name The identifier for the JavaScript library * - * @param string $url URL of the CSS file - * @param string $id An identifier for the CSS file * @return bool + * @since 1.8.0 */ -function elgg_register_css($url, $id = '') { - return elgg_register_external_file('css', $url, $id, 'head'); +function elgg_unregister_js($name) { + return elgg_unregister_external_file('js', $name); } /** - * Core registration function for external files + * Load a JavaScript resource on this page * - * @param string $type Type of external resource - * @param string $url URL - * @param string $id Identifier used as key - * @param string $location Location in the page to include the file - * @return bool + * This must be called before elgg_view_page(). It can be called before the + * script is registered. If you do not want a script loaded, unregister it. + * + * @param string $name Identifier of the JavaScript resource + * + * @return void + * @since 1.8.0 */ -function elgg_register_external_file($type, $url, $id, $location) { - global $CONFIG; - - if (empty($url)) { - return false; - } - - if (!isset($CONFIG->externals)) { - $CONFIG->externals = array(); - } - - if (!isset($CONFIG->externals[$type])) { - $CONFIG->externals[$type] = array(); - } - - if (!isset($CONFIG->externals[$type][$location])) { - $CONFIG->externals[$type][$location] = array(); - } - - if (!$id) { - $id = count($CONFIG->externals[$type][$location]); - } else { - $id = trim(strtolower($id)); - } - - $CONFIG->externals[$type][$location][$id] = elgg_normalize_url($url); - - return true; +function elgg_load_js($name) { + elgg_load_external_file('js', $name); } /** - * Unregister a JavaScript file + * Get the JavaScript URLs that are loaded * - * @param string $id The identifier for the JavaScript library - * @param string $url Optional URL to search for if id is not specified - * @param string $location Location in the page - * @return bool + * @param string $location 'head' or 'footer' + * + * @return array + * @since 1.8.0 */ -function elgg_unregister_js($id = '', $url = '', $location = 'head') { - return elgg_unregister_external_file('javascript', $id, $url, $location); +function elgg_get_loaded_js($location = 'head') { + return elgg_get_loaded_external_files('js', $location); } /** - * Unregister an external file + * Register a CSS file for inclusion in the HTML head + * + * @param string $name An identifier for the CSS file + * @param string $url URL of the CSS file + * @param int $priority Priority of the CSS file (lower numbers load earlier) * - * @param string $id The identifier of the CSS file - * @param string $url Optional URL to search for if id is not specified - * @param string $location Location in the page * @return bool + * @since 1.8.0 */ -function elgg_unregister_css($id = '', $url = '') { - return elgg_unregister_external_file('css', $id, $url, 'head'); +function elgg_register_css($name, $url, $priority = null) { + return elgg_register_external_file('css', $name, $url, 'head', $priority); } /** - * Unregister an external file + * Unregister a CSS file + * + * @param string $name The identifier for the CSS file * - * @param string $type Type of file: javascript or css - * @param string $id The identifier of the file - * @param string $url Optional URL to search for if the id is not specified - * @param string $location Location in the page * @return bool + * @since 1.8.0 */ -function elgg_unregister_external_file($type, $id = '', $url = '', $location = 'head') { - global $CONFIG; - - if (!isset($CONFIG->externals)) { - return false; - } - - if (!isset($CONFIG->externals[$type])) { - return false; - } - - if (!isset($CONFIG->externals[$type][$location])) { - return false; - } - - if (array_key_exists($id, $CONFIG->externals[$type][$location])) { - unset($CONFIG->externals[$type][$location][$id]); - return true; - } - - // was not registered with an id so do a search for the url - $key = array_search($url, $CONFIG->externals[$type][$location]); - if ($key) { - unset($CONFIG->externals[$type][$location][$key]); - return true; - } - - return false; +function elgg_unregister_css($name) { + return elgg_unregister_external_file('css', $name); } /** - * Get the JavaScript URLs + * Load a CSS file for this page * - * @return array + * This must be called before elgg_view_page(). It can be called before the + * CSS file is registered. If you do not want a CSS file loaded, unregister it. + * + * @param string $name Identifier of the CSS file + * + * @return void + * @since 1.8.0 */ -function elgg_get_js($location = 'head') { - return elgg_get_external_file('javascript', $location); +function elgg_load_css($name) { + elgg_load_external_file('css', $name); } /** - * Get the CSS URLs + * Get the loaded CSS URLs * * @return array + * @since 1.8.0 */ -function elgg_get_css() { - return elgg_get_external_file('css', 'head'); +function elgg_get_loaded_css() { + return elgg_get_loaded_external_files('css', 'head'); } /** - * Get external resource descriptors + * Core registration function for external files * - * @param string $type Type of resource - * @param string $location Page location - * @return array + * @param string $type Type of external resource (js or css) + * @param string $name Identifier used as key + * @param string $url URL + * @param string $location Location in the page to include the file + * @param int $priority Loading priority of the file + * + * @return bool + * @since 1.8.0 */ -function elgg_get_external_file($type, $location) { +function elgg_register_external_file($type, $name, $url, $location, $priority = 500) { global $CONFIG; - if (isset($CONFIG->externals) && - isset($CONFIG->externals[$type]) && - isset($CONFIG->externals[$type][$location])) { - - return array_values($CONFIG->externals[$type][$location]); + if (empty($name) || empty($url)) { + return false; + } + + $url = elgg_format_url($url); + $url = elgg_normalize_url($url); + + elgg_bootstrap_externals_data_structure($type); + + $name = trim(strtolower($name)); + + // normalize bogus priorities, but allow empty, null, and false to be defaults. + if (!is_numeric($priority)) { + $priority = 500; } - return array(); + + // no negative priorities right now. + $priority = max((int)$priority, 0); + + $item = elgg_extract($name, $CONFIG->externals_map[$type]); + + if ($item) { + // updating a registered item + // don't update loaded because it could already be set + $item->url = $url; + $item->location = $location; + + // if loaded before registered, that means it hasn't been added to the list yet + if ($CONFIG->externals[$type]->contains($item)) { + $priority = $CONFIG->externals[$type]->move($item, $priority); + } else { + $priority = $CONFIG->externals[$type]->add($item, $priority); + } + } else { + $item = new stdClass(); + $item->loaded = false; + $item->url = $url; + $item->location = $location; + + $priority = $CONFIG->externals[$type]->add($item, $priority); + } + + $CONFIG->externals_map[$type][$name] = $item; + + return $priority !== false; } /** - * Returns the HTML for "likes" and "like this" on entities. - * - * @param ElggEntity $entity The entity to like + * Unregister an external file * - * @return string|false The HTML for the likes, or false on failure + * @param string $type Type of file: js or css + * @param string $name The identifier of the file * - * @since 1.8 - * @see @elgg_view likes/forms/edit + * @return bool + * @since 1.8.0 */ -function elgg_view_likes($entity) { - if (!($entity instanceof ElggEntity)) { - return false; - } +function elgg_unregister_external_file($type, $name) { + global $CONFIG; - if ($likes = trigger_plugin_hook('likes', $entity->getType(), array('entity' => $entity), false)) { - return $likes; - } else { - $likes = elgg_view('likes/forms/edit', array('entity' => $entity)); - return $likes; + elgg_bootstrap_externals_data_structure($type); + + $name = trim(strtolower($name)); + $item = elgg_extract($name, $CONFIG->externals_map[$type]); + + if ($item) { + unset($CONFIG->externals_map[$type][$name]); + return $CONFIG->externals[$type]->remove($item); } + + return false; } /** - * Count the number of likes attached to an entity + * Load an external resource for use on this page * - * @param ElggEntity $entity The entity to count likes for + * @param string $type Type of file: js or css + * @param string $name The identifier for the file * - * @return int Number of likes - * @since 1.8 + * @return void + * @since 1.8.0 */ -function elgg_count_likes($entity) { - if ($likeno = trigger_plugin_hook('likes:count', $entity->getType(), - array('entity' => $entity), false)) { - return $likeno; +function elgg_load_external_file($type, $name) { + global $CONFIG; + + elgg_bootstrap_externals_data_structure($type); + + $name = trim(strtolower($name)); + + $item = elgg_extract($name, $CONFIG->externals_map[$type]); + + if ($item) { + // update a registered item + $item->loaded = true; } else { - return count_annotations($entity->getGUID(), "", "", "likes"); + $item = new stdClass(); + $item->loaded = true; + $item->url = ''; + $item->location = ''; + + $CONFIG->externals[$type]->add($item); + $CONFIG->externals_map[$type][$name] = $item; } } /** - * Count the number of comments attached to an entity + * Get external resource descriptors * - * @param ElggEntity $entity The entity to count comments for + * @param string $type Type of file: js or css + * @param string $location Page location * - * @return int Number of comments + * @return array + * @since 1.8.0 */ -function elgg_count_comments($entity) { - if ($commentno = trigger_plugin_hook('comments:count', $entity->getType(), - array('entity' => $entity), false)) { - return $commentno; - } else { - return count_annotations($entity->getGUID(), "", "", "generic_comment"); +function elgg_get_loaded_external_files($type, $location) { + global $CONFIG; + + if (isset($CONFIG->externals) && $CONFIG->externals[$type] instanceof ElggPriorityList) { + $items = $CONFIG->externals[$type]->getElements(); + + $callback = "return \$v->loaded == true && \$v->location == '$location';"; + $items = array_filter($items, create_function('$v', $callback)); + if ($items) { + array_walk($items, create_function('&$v,$k', '$v = $v->url;')); + } + return $items; } + return array(); } /** - * Returns all php files in a directory. - * - * @deprecated 1.7 Use elgg_get_file_list() instead + * Bootstraps the externals data structure in $CONFIG. * - * @param string $directory Directory to look in - * @param array $exceptions Array of extensions (with .!) to ignore - * @param array $list A list files to include in the return - * - * @return array + * @param string $type The type of external, js or css. + * @access private */ -function get_library_files($directory, $exceptions = array(), $list = array()) { - elgg_deprecated_notice('get_library_files() deprecated by elgg_get_file_list()', 1.7); - return elgg_get_file_list($directory, $exceptions, $list, array('.php')); +function elgg_bootstrap_externals_data_structure($type) { + global $CONFIG; + + if (!isset($CONFIG->externals)) { + $CONFIG->externals = array(); + } + + if (!isset($CONFIG->externals[$type]) || !$CONFIG->externals[$type] instanceof ElggPriorityList) { + $CONFIG->externals[$type] = new ElggPriorityList(); + } + + if (!isset($CONFIG->externals_map)) { + $CONFIG->externals_map = array(); + } + + if (!isset($CONFIG->externals_map[$type])) { + $CONFIG->externals_map[$type] = array(); + } } /** @@ -393,6 +496,8 @@ function sanitise_filepath($path, $append_slash = TRUE) { // Convert to correct UNIX paths $path = str_replace('\\', '/', $path); $path = str_replace('../', '/', $path); + // replace // with / except when preceeded by : + $path = preg_replace("/([^:])\/\//", "$1/", $path); // Sort trailing slash $path = trim($path); @@ -407,125 +512,6 @@ function sanitise_filepath($path, $append_slash = TRUE) { } /** - * Adds an entry in $CONFIG[$register_name][$subregister_name]. - * - * This is only used for the site-wide menu. See {@link add_menu()}. - * - * @param string $register_name The name of the top-level register - * @param string $subregister_name The name of the subregister - * @param mixed $subregister_value The value of the subregister - * @param array $children_array Optionally, an array of children - * - * @return true|false Depending on success - * @todo Can be deprecated when the new menu system is introduced. - */ -function add_to_register($register_name, $subregister_name, $subregister_value, -$children_array = array()) { - - global $CONFIG; - - if (empty($register_name) || empty($subregister_name)) { - return false; - } - - if (!isset($CONFIG->registers)) { - $CONFIG->registers = array(); - } - - if (!isset($CONFIG->registers[$register_name])) { - $CONFIG->registers[$register_name] = array(); - } - - $subregister = new stdClass; - $subregister->name = $subregister_name; - $subregister->value = $subregister_value; - - if (is_array($children_array)) { - $subregister->children = $children_array; - } - - $CONFIG->registers[$register_name][$subregister_name] = $subregister; - return true; -} - -/** - * Removes a register entry from $CONFIG[register_name][subregister_name] - * - * This is used to by {@link remove_menu()} to remove site-wide menu items. - * - * @param string $register_name The name of the top-level register - * @param string $subregister_name The name of the subregister - * - * @return true|false Depending on success - * @since 1.7.0 - * @todo Can be deprecated when the new menu system is introduced. - */ -function remove_from_register($register_name, $subregister_name) { - global $CONFIG; - - if (empty($register_name) || empty($subregister_name)) { - return false; - } - - if (!isset($CONFIG->registers)) { - return false; - } - - if (!isset($CONFIG->registers[$register_name])) { - return false; - } - - if (isset($CONFIG->registers[$register_name][$subregister_name])) { - unset($CONFIG->registers[$register_name][$subregister_name]); - return true; - } - - return false; -} - -/** - * Constructs and returns a register object. - * - * @param string $register_name The name of the register - * @param mixed $register_value The value of the register - * @param array $children_array Optionally, an array of children - * - * @return false|stdClass Depending on success - * @todo Can be deprecated when the new menu system is introduced. - */ -function make_register_object($register_name, $register_value, $children_array = array()) { - elgg_deprecated_notice('make_register_object() is deprecated by add_submenu_item()', 1.7); - if (empty($register_name) || empty($register_value)) { - return false; - } - - $register = new stdClass; - $register->name = $register_name; - $register->value = $register_value; - $register->children = $children_array; - - return $register; -} - -/** - * If it exists, returns a particular register as an array - * - * @param string $register_name The name of the register - * - * @return array|false Depending on success - * @todo Can be deprecated when the new menu system is introduced. - */ -function get_register($register_name) { - global $CONFIG; - - if (isset($CONFIG->registers[$register_name])) { - return $CONFIG->registers[$register_name]; - } - - return false; -} - -/** * Queues a message to be displayed. * * Messages will not be displayed immediately, but are stored in @@ -533,7 +519,7 @@ function get_register($register_name) { * * The method of displaying these messages differs depending upon plugins and * viewtypes. The core default viewtype retrieves messages in - * {@link views/default/page_shells/default.php} and displays messages as + * {@link views/default/page/shells/default.php} and displays messages as * javascript popups. * * @internal Messages are stored as strings in the $_SESSION['msg'][$register] array. @@ -547,14 +533,14 @@ function get_register($register_name) { * 'messages') as well as {@link register_error()} messages ($register = 'errors'). * * @param mixed $message Optionally, a single message or array of messages to add, (default: null) - * @param string $register Types of message: "errors", "messages" (default: messages) + * @param string $register Types of message: "error", "success" (default: success) * @param bool $count Count the number of messages (default: false) * - * @return true|false|array Either the array of messages, or a response regarding + * @return bool|array Either the array of messages, or a response regarding * whether the message addition was successful. * @todo Clean up. Separate registering messages and retrieving them. */ -function system_messages($message = null, $register = "messages", $count = false) { +function system_messages($message = null, $register = "success", $count = false) { if (!isset($_SESSION['msg'])) { $_SESSION['msg'] = array(); } @@ -584,7 +570,7 @@ function system_messages($message = null, $register = "messages", $count = false return sizeof($_SESSION['msg'][$register]); } else { $count = 0; - foreach ($_SESSION['msg'] as $register => $submessages) { + foreach ($_SESSION['msg'] as $submessages) { $count += sizeof($submessages); } return $count; @@ -611,10 +597,10 @@ function count_messages($register = "") { * * @param string|array $message Message or messages to add * - * @return Bool + * @return bool */ function system_message($message) { - return system_messages($message, "messages"); + return system_messages($message, "success"); } /** @@ -624,36 +610,10 @@ function system_message($message) { * * @param string|array $error Error or errors to add * - * @return true|false Success response + * @return bool */ function register_error($error) { - return system_messages($error, "errors"); -} - -/** - * Deprecated events core function. Code divided between register_elgg_event_handler() - * and trigger_elgg_event(). - * - * @param string $event The type of event (eg 'init', 'update', 'delete') - * @param string $object_type The type of object (eg 'system', 'blog', 'user') - * @param string $function The name of the function that will handle the event - * @param int $priority Priority to call handler. Lower numbers called first (default 500) - * @param boolean $call Set to true to call the event rather than add to it (default false) - * @param mixed $object Optionally, the object the event is being performed on (eg a user) - * - * @return true|false Depending on success - */ -function events($event = "", $object_type = "", $function = "", $priority = 500, -$call = false, $object = null) { - - elgg_deprecated_notice('events() has been deprecated.', 1.8); - - // leaving this here just in case someone was directly calling this internal function - if (!$call) { - return register_elgg_event_handler($event, $object_type, $function, $priority); - } else { - return trigger_elgg_event($event, $object_type, $object); - } + return system_messages($error, "error"); } /** @@ -706,7 +666,7 @@ $call = false, $object = null) { * @param string $event The event type * @param string $object_type The object type * @param string $callback The handler callback - * @param int $priority The priority of the event + * @param int $priority The priority - 0 is default, negative before, positive after * * @return bool * @link http://docs.elgg.org/Tutorials/Plugins/Events @@ -715,11 +675,11 @@ $call = false, $object = null) { * callback and halting execution. * @example events/all.php Example of how to use the 'all' keyword. */ -function register_elgg_event_handler($event, $object_type, $callback, $priority = 500) { +function elgg_register_event_handler($event, $object_type, $callback, $priority = 500) { global $CONFIG; if (empty($event) || empty($object_type)) { - return FALSE; + return false; } if (!isset($CONFIG->events)) { @@ -732,20 +692,18 @@ function register_elgg_event_handler($event, $object_type, $callback, $priority $CONFIG->events[$event][$object_type] = array(); } - if (!is_callable($callback)) { - return FALSE; + if (!is_callable($callback, true)) { + return false; } - $priority = (int) $priority; - if ($priority < 0) { - $priority = 0; - } + $priority = max((int) $priority, 0); + while (isset($CONFIG->events[$event][$object_type][$priority])) { $priority++; } $CONFIG->events[$event][$object_type][$priority] = $callback; ksort($CONFIG->events[$event][$object_type]); - return TRUE; + return true; } /** @@ -756,13 +714,16 @@ function register_elgg_event_handler($event, $object_type, $callback, $priority * @param string $callback The callback * * @return void - * @since 1.7.0 + * @since 1.7 */ -function unregister_elgg_event_handler($event, $object_type, $callback) { +function elgg_unregister_event_handler($event, $object_type, $callback) { global $CONFIG; - foreach ($CONFIG->events[$event][$object_type] as $key => $event_callback) { - if ($event_callback == $callback) { - unset($CONFIG->events[$event][$object_type][$key]); + + if (isset($CONFIG->events[$event]) && isset($CONFIG->events[$event][$object_type])) { + foreach ($CONFIG->events[$event][$object_type] as $key => $event_callback) { + if ($event_callback == $callback) { + unset($CONFIG->events[$event][$object_type][$key]); + } } } } @@ -785,7 +746,7 @@ function unregister_elgg_event_handler($event, $object_type, $callback) { * @tip When referring to events, the preferred syntax is "event, type". * * @internal Only rarely should events be changed, added, or removed in core. - * When making changes to events, be sure to first create a ticket in trac. + * When making changes to events, be sure to first create a ticket on Github. * * @internal @tip Think of $object_type as the primary namespace element, and * $event as the secondary namespace. @@ -798,49 +759,43 @@ function unregister_elgg_event_handler($event, $object_type, $callback) { * @link http://docs.elgg.org/Tutorials/Core/Events * @internal @example events/emit.php Basic emitting of an Elgg event. */ -function trigger_elgg_event($event, $object_type, $object = null) { +function elgg_trigger_event($event, $object_type, $object = null) { global $CONFIG; - if (!empty($CONFIG->events[$event][$object_type]) && is_array($CONFIG->events[$event][$object_type])) { - foreach ($CONFIG->events[$event][$object_type] as $callback) { - if (call_user_func_array($callback, array($event, $object_type, $object)) === FALSE) { - return FALSE; - } - } + $events = array(); + if (isset($CONFIG->events[$event][$object_type])) { + $events[] = $CONFIG->events[$event][$object_type]; } - - if (!empty($CONFIG->events['all'][$object_type]) && is_array($CONFIG->events['all'][$object_type])) { - foreach ($CONFIG->events['all'][$object_type] as $callback) { - if (call_user_func_array($callback, array($event, $object_type, $object)) === FALSE) { - return FALSE; - } - } + if (isset($CONFIG->events['all'][$object_type])) { + $events[] = $CONFIG->events['all'][$object_type]; } - - if (!empty($CONFIG->events[$event]['all']) && is_array($CONFIG->events[$event]['all'])) { - foreach ($CONFIG->events[$event]['all'] as $callback) { - if (call_user_func_array($callback, array($event, $object_type, $object)) === FALSE) { - return FALSE; - } - } + if (isset($CONFIG->events[$event]['all'])) { + $events[] = $CONFIG->events[$event]['all']; + } + if (isset($CONFIG->events['all']['all'])) { + $events[] = $CONFIG->events['all']['all']; } - if (!empty($CONFIG->events['all']['all']) && is_array($CONFIG->events['all']['all'])) { - foreach ($CONFIG->events['all']['all'] as $callback) { - if (call_user_func_array($callback, array($event, $object_type, $object)) === FALSE) { - return FALSE; + $args = array($event, $object_type, $object); + + foreach ($events as $callback_list) { + if (is_array($callback_list)) { + foreach ($callback_list as $callback) { + if (is_callable($callback) && (call_user_func_array($callback, $args) === false)) { + return false; + } } } } - return TRUE; + return true; } /** * Register a callback as a plugin hook handler. * * Plugin hooks allow developers to losely couple plugins and features by - * repsonding to and emitting {@link trigger_plugin_hook()} customizable hooks. + * repsonding to and emitting {@link elgg_trigger_plugin_hook()} customizable hooks. * Handler callbacks can respond to the hook, change the details of the hook, or * ignore it. * @@ -848,7 +803,7 @@ function trigger_elgg_event($event, $object_type, $object = null) { * is called in order of priority. If the return value of a handler is not * null, that value is passed to the next callback in the call stack. When all * callbacks have been run, the final value is passed back to the caller - * via {@link trigger_plugin_hook()}. + * via {@link elgg_trigger_plugin_hook()}. * * Similar to Elgg Events, plugin hook handler callbacks are registered by passing * a hook, a type, and a priority. @@ -892,20 +847,21 @@ function trigger_elgg_event($event, $object_type, $object = null) { * * @param string $hook The name of the hook * @param string $type The type of the hook - * @param callback $callback The name of a valid function or an array with object and method - * @param string $priority The priority - 0 is first, 1000 last, default is 500 + * @param callable $callback The name of a valid function or an array with object and method + * @param int $priority The priority - 500 is default, lower numbers called first * * @return bool * * @example hooks/register/basic.php Registering for a plugin hook and examining the variables. * @example hooks/register/advanced.php Registering for a plugin hook and changing the params. * @link http://docs.elgg.org/Tutorials/Plugins/Hooks + * @since 1.8.0 */ -function register_plugin_hook($hook, $type, $callback, $priority = 500) { +function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority = 500) { global $CONFIG; if (empty($hook) || empty($type)) { - return FALSE; + return false; } if (!isset($CONFIG->hooks)) { @@ -918,20 +874,18 @@ function register_plugin_hook($hook, $type, $callback, $priority = 500) { $CONFIG->hooks[$hook][$type] = array(); } - if (!is_callable($callback)) { - return FALSE; + if (!is_callable($callback, true)) { + return false; } - $priority = (int) $priority; - if ($priority < 0) { - $priority = 0; - } + $priority = max((int) $priority, 0); + while (isset($CONFIG->hooks[$hook][$type][$priority])) { $priority++; } $CONFIG->hooks[$hook][$type][$priority] = $callback; ksort($CONFIG->hooks[$hook][$type]); - return TRUE; + return true; } /** @@ -939,16 +893,19 @@ function register_plugin_hook($hook, $type, $callback, $priority = 500) { * * @param string $hook The name of the hook * @param string $entity_type The name of the type of entity (eg "user", "object" etc) - * @param callback $callback The PHP callback to be removed + * @param callable $callback The PHP callback to be removed * * @return void - * @since 1.7.0 + * @since 1.8.0 */ -function unregister_plugin_hook($hook, $entity_type, $callback) { +function elgg_unregister_plugin_hook_handler($hook, $entity_type, $callback) { global $CONFIG; - foreach ($CONFIG->hooks[$hook][$entity_type] as $key => $hook_callback) { - if ($hook_callback == $callback) { - unset($CONFIG->hooks[$hook][$entity_type][$key]); + + if (isset($CONFIG->hooks[$hook]) && isset($CONFIG->hooks[$hook][$entity_type])) { + foreach ($CONFIG->hooks[$hook][$entity_type] as $key => $hook_callback) { + if ($hook_callback == $callback) { + unset($CONFIG->hooks[$hook][$entity_type][$key]); + } } } } @@ -975,7 +932,13 @@ function unregister_plugin_hook($hook, $entity_type, $callback) { * called for all hooks of type $event, regardless of $object_type. If $hook * and $type both are 'all', the handler will be called for all hooks. * - * @see register_plugin_hook() + * @internal The checks for $hook and/or $type not being equal to 'all' is to + * prevent a plugin hook being registered with an 'all' being called more than + * once if the trigger occurs with an 'all'. An example in core of this is in + * actions.php: + * elgg_trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', ...) + * + * @see elgg_register_plugin_hook_handler() * * @param string $hook The name of the hook to trigger ("all" will * trigger for all $types regardless of $hook value) @@ -992,46 +955,42 @@ function unregister_plugin_hook($hook, $entity_type, $callback) { * the results to populate a menu. * @example hooks/basic.php Trigger and respond to a basic plugin hook. * @link http://docs.elgg.org/Tutorials/Plugins/Hooks + * + * @since 1.8.0 */ -function trigger_plugin_hook($hook, $type, $params = null, $returnvalue = null) { +function elgg_trigger_plugin_hook($hook, $type, $params = null, $returnvalue = null) { global $CONFIG; - if (!empty($CONFIG->hooks[$hook][$type]) && is_array($CONFIG->hooks[$hook][$type])) { - foreach ($CONFIG->hooks[$hook][$type] as $hookcallback) { - $temp_return_value = call_user_func_array($hookcallback, - array($hook, $type, $returnvalue, $params)); - if (!is_null($temp_return_value)) { - $returnvalue = $temp_return_value; - } + $hooks = array(); + if (isset($CONFIG->hooks[$hook][$type])) { + if ($hook != 'all' && $type != 'all') { + $hooks[] = $CONFIG->hooks[$hook][$type]; } } - - if (!empty($CONFIG->hooks['all'][$type]) && is_array($CONFIG->hooks['all'][$type])) { - foreach ($CONFIG->hooks['all'][$type] as $hookcallback) { - $temp_return_value = call_user_func_array($hookcallback, - array($hook, $type, $returnvalue, $params)); - if (!is_null($temp_return_value)) { - $returnvalue = $temp_return_value; - } + if (isset($CONFIG->hooks['all'][$type])) { + if ($type != 'all') { + $hooks[] = $CONFIG->hooks['all'][$type]; } } - - if (!empty($CONFIG->hooks[$hook]['all']) && is_array($CONFIG->hooks[$hook]['all'])) { - foreach ($CONFIG->hooks[$hook]['all'] as $hookcallback) { - $temp_return_value = call_user_func_array($hookcallback, - array($hook, $type, $returnvalue, $params)); - if (!is_null($temp_return_value)) { - $returnvalue = $temp_return_value; - } + if (isset($CONFIG->hooks[$hook]['all'])) { + if ($hook != 'all') { + $hooks[] = $CONFIG->hooks[$hook]['all']; } } + if (isset($CONFIG->hooks['all']['all'])) { + $hooks[] = $CONFIG->hooks['all']['all']; + } - if (!empty($CONFIG->hooks['all']['all']) && is_array($CONFIG->hooks['all']['all'])) { - foreach ($CONFIG->hooks['all']['all'] as $hookcallback) { - $temp_return_value = call_user_func_array($hookcallback, - array($hook, $type, $returnvalue, $params)); - if (!is_null($temp_return_value)) { - $returnvalue = $temp_return_value; + foreach ($hooks as $callback_list) { + if (is_array($callback_list)) { + foreach ($callback_list as $hookcallback) { + if (is_callable($hookcallback)) { + $args = array($hook, $type, $returnvalue, $params); + $temp_return_value = call_user_func_array($hookcallback, $args); + if (!is_null($temp_return_value)) { + $returnvalue = $temp_return_value; + } + } } } } @@ -1040,7 +999,7 @@ function trigger_plugin_hook($hook, $type, $params = null, $returnvalue = null) } /** - * Intercepts, logs, and display uncaught exceptions. + * Intercepts, logs, and displays uncaught exceptions. * * @warning This function should never be called directly. * @@ -1049,9 +1008,11 @@ function trigger_plugin_hook($hook, $type, $params = null, $returnvalue = null) * @param Exception $exception The exception being handled * * @return void + * @access private */ function _elgg_php_exception_handler($exception) { - error_log("*** FATAL EXCEPTION *** : " . $exception); + $timestamp = time(); + error_log("Exception #$timestamp: $exception"); // Wipe any existing output buffer ob_end_clean(); @@ -1060,11 +1021,31 @@ function _elgg_php_exception_handler($exception) { header("Cache-Control: no-cache, must-revalidate", true); header('Expires: Fri, 05 Feb 1982 00:00:00 -0500', true); // @note Do not send a 500 header because it is not a server error - //header("Internal Server Error", true, 500); - elgg_set_viewtype('failsafe'); - $body = elgg_view("messages/exceptions/exception", array('object' => $exception)); - echo elgg_view_page(elgg_echo('exception:title'), $body); + try { + // we don't want the 'pagesetup', 'system' event to fire + global $CONFIG; + $CONFIG->pagesetupdone = true; + + elgg_set_viewtype('failsafe'); + if (elgg_is_admin_logged_in()) { + $body = elgg_view("messages/exceptions/admin_exception", array( + 'object' => $exception, + 'ts' => $timestamp + )); + } else { + $body = elgg_view("messages/exceptions/exception", array( + 'object' => $exception, + 'ts' => $timestamp + )); + } + echo elgg_view_page(elgg_echo('exception:title'), $body); + } catch (Exception $e) { + $timestamp = time(); + $message = $e->getMessage(); + echo "Fatal error in exception handler. Check log for Exception #$timestamp"; + error_log("Exception #$timestamp : fatal error in exception handler : $message"); + } } /** @@ -1087,6 +1068,9 @@ function _elgg_php_exception_handler($exception) { * @param array $vars An array that points to the active symbol table where error occurred * * @return true + * @throws Exception + * @access private + * @todo Replace error_log calls with elgg_log calls. */ function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) { $error = date("Y-m-d H:i:s (T)") . ": \"$errmsg\" in file $filename (line $linenum)"; @@ -1102,7 +1086,12 @@ function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) { case E_WARNING : case E_USER_WARNING : - error_log("PHP WARNING: $error"); + case E_RECOVERABLE_ERROR: // (e.g. type hint violation) + + // check if the error wasn't suppressed by the error control operator (@) + if (error_reporting()) { + error_log("PHP WARNING: $error"); + } break; default: @@ -1126,8 +1115,8 @@ function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) { * * @note No messages will be displayed unless debugging has been enabled. * - * @param str $message User message - * @param str $level NOTICE | WARNING | ERROR | DEBUG + * @param string $message User message + * @param string $level NOTICE | WARNING | ERROR | DEBUG * * @return bool * @since 1.7.0 @@ -1189,10 +1178,12 @@ function elgg_dump($value, $to_screen = TRUE, $level = 'NOTICE') { global $CONFIG; // plugin can return false to stop the default logging method - $params = array('level' => $level, - 'msg' => $value, - 'to_screen' => $to_screen); - if (!trigger_plugin_hook('debug', 'log', $params, true)) { + $params = array( + 'level' => $level, + 'msg' => $value, + 'to_screen' => $to_screen, + ); + if (!elgg_trigger_plugin_hook('debug', 'log', $params, true)) { return; } @@ -1203,6 +1194,11 @@ function elgg_dump($value, $to_screen = TRUE, $level = 'NOTICE') { $to_screen = FALSE; } + // Do not want to write to JS or CSS pages + if (elgg_in_context('js') || elgg_in_context('css')) { + $to_screen = FALSE; + } + if ($to_screen == TRUE) { echo '<pre>'; print_r($value); @@ -1217,7 +1213,9 @@ function elgg_dump($value, $to_screen = TRUE, $level = 'NOTICE') { * * This function either displays or logs the deprecation message, * depending upon the deprecation policies in {@link CODING.txt}. - * Logged messages are sent with the level of 'WARNING'. + * Logged messages are sent with the level of 'WARNING'. Only admins + * get visual deprecation notices. When non-admins are logged in, the + * notices are sent to PHP's log through elgg_dump(). * * A user-visual message will be displayed if $dep_version is greater * than 1 minor releases lower than the current Elgg version, or at all @@ -1228,233 +1226,77 @@ function elgg_dump($value, $to_screen = TRUE, $level = 'NOTICE') { * * @see CODING.txt * - * @param str $msg Message to log / display. - * @param str $dep_version Human-readable *release* version: 1.7, 1.7.3 + * @param string $msg Message to log / display. + * @param string $dep_version Human-readable *release* version: 1.7, 1.8, ... + * @param int $backtrace_level How many levels back to display the backtrace. + * Useful if calling from functions that are called + * from other places (like elgg_view()). Set to -1 + * for a full backtrace. * * @return bool * @since 1.7.0 */ -function elgg_deprecated_notice($msg, $dep_version) { +function elgg_deprecated_notice($msg, $dep_version, $backtrace_level = 1) { // if it's a major release behind, visual and logged - // if it's a 2 minor releases behind, visual and logged - // if it's 1 minor release behind, logged. - // bugfixes don't matter because you're not deprecating between them, RIGHT? + // if it's a 1 minor release behind, visual and logged + // if it's for current minor release, logged. + // bugfixes don't matter because we are not deprecating between them + if (!$dep_version) { - return FALSE; + return false; } - $elgg_version = get_version(TRUE); + $elgg_version = get_version(true); $elgg_version_arr = explode('.', $elgg_version); - $elgg_major_version = $elgg_version_arr[0]; - $elgg_minor_version = $elgg_version_arr[1]; - - $dep_version_arr = explode('.', $dep_version); - $dep_major_version = $dep_version_arr[0]; - $dep_minor_version = $dep_version_arr[1]; + $elgg_major_version = (int)$elgg_version_arr[0]; + $elgg_minor_version = (int)$elgg_version_arr[1]; - $last_working_version = $dep_minor_version - 1; + $dep_major_version = (int)$dep_version; + $dep_minor_version = 10 * ($dep_version - $dep_major_version); - $visual = FALSE; + $visual = false; - // use version_compare to account for 1.7a < 1.7 - if (($dep_major_version < $elgg_major_version) - || (($elgg_minor_version - $last_working_version) > 1)) { - $visual = TRUE; + if (($dep_major_version < $elgg_major_version) || + ($dep_minor_version < $elgg_minor_version)) { + $visual = true; } - $msg = "Deprecated in $dep_version: $msg"; + $msg = "Deprecated in $dep_major_version.$dep_minor_version: $msg"; - if ($visual) { + if ($visual && elgg_is_admin_logged_in()) { register_error($msg); } // Get a file and line number for the log. Never show this in the UI. // Skip over the function that sent this notice and see who called the deprecated // function itself. + $msg .= " Called from "; + $stack = array(); $backtrace = debug_backtrace(); - $caller = $backtrace[1]; - $msg .= " (Called from {$caller['file']}:{$caller['line']})"; - - elgg_log($msg, 'WARNING'); - - return TRUE; -} + // never show this call. + array_shift($backtrace); + $i = count($backtrace); + foreach ($backtrace as $trace) { + $stack[] = "[#$i] {$trace['file']}:{$trace['line']}"; + $i--; -/** - * Checks if code is being called from a certain function. - * - * To use, call this function with the function name (and optional - * file location) that it has to be called from, it will either - * return true or false. - * - * e.g. - * - * function my_secure_function() - * { - * if (!call_gatekeeper("my_call_function")) - * return false; - * - * ... do secure stuff ... - * } - * - * function my_call_function() - * { - * // will work - * my_secure_function(); - * } - * - * function bad_function() - * { - * // Will not work - * my_secure_function(); - * } - * - * @param mixed $function The function that this function must have in its call stack, - * to test against a method pass an array containing a class and - * method name. - * @param string $file Optional file that the function must reside in. - * - * @return bool - * @todo This is neat but is it necessary? - */ -function call_gatekeeper($function, $file = "") { - // Sanity check - if (!$function) { - return false; - } - - // Check against call stack to see if this is being called from the correct location - $callstack = debug_backtrace(); - $stack_element = false; - - foreach ($callstack as $call) { - if (is_array($function)) { - if ( - (strcmp($call['class'], $function[0]) == 0) && - (strcmp($call['function'], $function[1]) == 0) - ) { - $stack_element = $call; - } - } else { - if (strcmp($call['function'], $function) == 0) { - $stack_element = $call; + if ($backtrace_level > 0) { + if ($backtrace_level <= 1) { + break; } + $backtrace_level--; } } - if (!$stack_element) { - return false; - } - - // If file then check that this it is being called from this function - if ($file) { - $mirror = null; + $msg .= implode("<br /> -> ", $stack); - if (is_array($function)) { - $mirror = new ReflectionMethod($function[0], $function[1]); - } else { - $mirror = new ReflectionFunction($function); - } - - if ((!$mirror) || (strcmp($file, $mirror->getFileName()) != 0)) { - return false; - } - } + elgg_log($msg, 'WARNING'); return true; } /** - * This function checks to see if it is being called at somepoint by a function defined somewhere - * on a given path (optionally including subdirectories). - * - * This function is similar to call_gatekeeper() but returns true if it is being called - * by a method or function which has been defined on a given path or by a specified file. - * - * @param string $path The full path and filename that this function must have - * in its call stack If a partial path is given and - * $include_subdirs is true, then the function will return - * true if called by any function in or below the specified path. - * @param bool $include_subdirs Are subdirectories of the path ok, or must you specify an - * absolute path and filename. - * @param bool $strict_mode If true then the calling method or function must be directly - * called by something on $path, if false the whole call stack is - * searched. - * - * @todo Again, very neat, but is it necessary? - * - * @return void - */ -function callpath_gatekeeper($path, $include_subdirs = true, $strict_mode = false) { - global $CONFIG; - - $path = sanitise_string($path); - - if ($path) { - $callstack = debug_backtrace(); - - foreach ($callstack as $call) { - $call['file'] = str_replace("\\", "/", $call['file']); - - if ($include_subdirs) { - if (strpos($call['file'], $path) === 0) { - - if ($strict_mode) { - $callstack[1]['file'] = str_replace("\\", "/", $callstack[1]['file']); - if ($callstack[1] === $call) { - return true; - } - } else { - return true; - } - } - } else { - if (strcmp($path, $call['file']) == 0) { - if ($strict_mode) { - if ($callstack[1] === $call) { - return true; - } - } else { - return true; - } - } - } - - } - return false; - } - - if (isset($CONFIG->debug)) { - system_message("Gatekeeper'd function called from {$callstack[1]['file']}:" - . "{$callstack[1]['line']}\n\nStack trace:\n\n" . print_r($callstack, true)); - } - - return false; -} - -/** - * Get the URL for the current (or specified) site - * - * @param int $site_guid The GUID of the site whose URL we want to grab - * @return string - */ -function elgg_get_site_url($site_guid = 0) { - if ($site_guid == 0) { - global $CONFIG; - return $CONFIG->wwwroot; - } - - $site = get_entity($site_guid); - - if (!$site instanceof ElggSite) { - return false; - } - - return $site->url; -} - -/** * Returns the current page's complete URL. * * The current URL is assembled using the network's wwwroot and the request URI @@ -1464,8 +1306,6 @@ function elgg_get_site_url($site_guid = 0) { * @return string The current page URL. */ function current_page_url() { - global $CONFIG; - $url = parse_url(elgg_get_site_url()); $page = $url['scheme'] . "://"; @@ -1510,7 +1350,7 @@ function full_url() { "" : (":" . $_SERVER["SERVER_PORT"]); // This is here to prevent XSS in poorly written browsers used by 80% of the population. - // {@trac [5813]} + // https://github.com/Elgg/Elgg/commit/0c947e80f512cb0a482b1864fd0a6965c8a0cd4a $quotes = array('\'', '"'); $encoded = array('%27', '%22'); @@ -1526,7 +1366,7 @@ function full_url() { * @param array $parts Associative array of URL components like parse_url() returns * @param bool $html_encode HTML Encode the url? * - * @return str Full URL + * @return string Full URL * @since 1.7.0 */ function elgg_http_build_url(array $parts, $html_encode = TRUE) { @@ -1557,14 +1397,14 @@ function elgg_http_build_url(array $parts, $html_encode = TRUE) { * add tokens to the action. The form view automatically handles * tokens. * - * @param str $url Full action URL - * @param bool $html_encode HTML encode the url? + * @param string $url Full action URL + * @param bool $html_encode HTML encode the url? (default: false) * - * @return str URL with action tokens + * @return string URL with action tokens * @since 1.7.0 * @link http://docs.elgg.org/Tutorials/Actions */ -function elgg_add_action_tokens_to_url($url, $html_encode = TRUE) { +function elgg_add_action_tokens_to_url($url, $html_encode = FALSE) { $components = parse_url(elgg_normalize_url($url)); if (isset($components['query'])) { @@ -1586,23 +1426,6 @@ function elgg_add_action_tokens_to_url($url, $html_encode = TRUE) { return elgg_http_build_url($components, $html_encode); } - -/** - * Add action tokens to URL. - * - * @param string $url URL - * - * @return string - * - * @deprecated 1.7 final - */ -function elgg_validate_action_url($url) { - elgg_deprecated_notice('elgg_validate_action_url() deprecated by elgg_add_action_tokens_to_url().', - '1.7b'); - - return elgg_add_action_tokens_to_url($url); -} - /** * Removes an element from a URL's query string. * @@ -1629,17 +1452,17 @@ function elgg_http_remove_url_query_element($url, $element) { } $url_array['query'] = http_build_query($query); - $string = elgg_http_build_url($url_array); + $string = elgg_http_build_url($url_array, false); return $string; } /** * Adds an element or elements to a URL's query string. * - * @param str $url The URL - * @param array $elements Key/value pairs to add to the URL + * @param string $url The URL + * @param array $elements Key/value pairs to add to the URL * - * @return str The new URL with the query strings added + * @return string The new URL with the query strings added * @since 1.7.0 */ function elgg_http_add_url_query_elements($url, array $elements) { @@ -1656,7 +1479,7 @@ function elgg_http_add_url_query_elements($url, array $elements) { } $url_array['query'] = http_build_query($query); - $string = elgg_http_build_url($url_array); + $string = elgg_http_build_url($url_array, false); return $string; } @@ -1672,13 +1495,12 @@ function elgg_http_add_url_query_elements($url, array $elements) { * @param string $url2 Second URL * @param array $ignore_params GET params to ignore in the comparison * - * @return BOOL - * @since 1.8 + * @return bool + * @since 1.8.0 */ function elgg_http_url_is_identical($url1, $url2, $ignore_params = array('offset', 'limit')) { - global $CONFIG; - // if the server portion is missing but it starts with / then add the url in. + // @todo use elgg_normalize_url() if (elgg_substr($url1, 0, 1) == '/') { $url1 = elgg_get_site_url() . ltrim($url1, '/'); } @@ -1696,8 +1518,12 @@ function elgg_http_url_is_identical($url1, $url2, $ignore_params = array('offset $url1_info = parse_url($url1); $url2_info = parse_url($url2); - $url1_info['path'] = trim($url1_info['path'], '/'); - $url2_info['path'] = trim($url2_info['path'], '/'); + if (isset($url1_info['path'])) { + $url1_info['path'] = trim($url1_info['path'], '/'); + } + if (isset($url2_info['path'])) { + $url2_info['path'] = trim($url2_info['path'], '/'); + } // compare basic bits $parts = array('scheme', 'host', 'path'); @@ -1760,132 +1586,6 @@ function elgg_http_url_is_identical($url1, $url2, $ignore_params = array('offset } /** - * Load all the REQUEST variables into the sticky form cache - * - * Call this from an action when you want all your submitted variables - * available if the submission fails validation and is sent back to the form - * - * @param string $form_name Name of the sticky form - * - * @return void - * @link http://docs.elgg.org/Tutorials/UI/StickyForms - */ -function elgg_make_sticky_form($form_name) { - global $CONFIG; - - $CONFIG->active_sticky_form = $form_name; - elgg_clear_sticky_form($form_name); - - if (!isset($_SESSION['sticky_forms'])) { - $_SESSION['sticky_forms'] = array(); - } - $_SESSION['sticky_forms'][$form_name] = array(); - - foreach ($_REQUEST as $key => $var) { - // will go through XSS filtering on the get function - $_SESSION['sticky_forms'][$form_name][$key] = $var; - } -} - -/** - * Clear the sticky form cache - * - * Call this if validation is successful in the action handler or - * when they sticky values have been used to repopulate the form - * after a validation error. - * - * @param string $form_name Form namespace - * - * @return void - * @link http://docs.elgg.org/Tutorials/UI/StickyForms - */ -function elgg_clear_sticky_form($form_name) { - unset($_SESSION['sticky_forms'][$form_name]); -} - -/** - * Has this form been made sticky? - * - * @param string $form_name Form namespace - * - * @return boolean - * @link http://docs.elgg.org/Tutorials/UI/StickyForms - */ -function elgg_is_sticky_form($form_name) { - return isset($_SESSION['sticky_forms'][$form_name]); -} - -/** - * Get a specific sticky variable - * - * @param string $form_name The name of the form - * @param string $variable The name of the variable - * @param mixed $default Default value if the variable does not exist in sticky cache - * @param boolean $filter_result Filter for bad input if true - * - * @return mixed - * - * @todo should this filter the default value? - * @link http://docs.elgg.org/Tutorials/UI/StickyForms - */ -function elgg_get_sticky_value($form_name, $variable = '', $default = NULL, $filter_result = true) { - if (isset($_SESSION['sticky_forms'][$form_name][$variable])) { - $value = $_SESSION['sticky_forms'][$form_name][$variable]; - if ($filter_result) { - // XSS filter result - $value = filter_tags($value); - } - return $value; - } - return $default; -} - -/** - * Clear a specific sticky variable - * - * @param string $form_name The name of the form - * @param string $variable The name of the variable to clear - * - * @return void - * @link http://docs.elgg.org/Tutorials/UI/StickyForms - */ -function elgg_clear_sticky_value($form_name, $variable) { - unset($_SESSION['sticky_forms'][$form_name][$variable]); -} - -/** - * Returns the current active sticky form. - * - * @return mixed Str | FALSE - * @link http://docs.elgg.org/Tutorials/UI/StickyForms - */ -function elgg_get_active_sticky_form() { - global $CONFIG; - - if (isset($CONFIG->active_sticky_form)) { - $form_name = $CONFIG->active_sticky_form; - } else { - return FALSE; - } - - return (elgg_is_sticky_form($form_name)) ? $form_name : FALSE; -} - -/** - * Sets the active sticky form. - * - * @param string $form_name The name of the form - * - * @return void - * @link http://docs.elgg.org/Tutorials/UI/StickyForms - */ -function elgg_set_active_sticky_form($form_name) { - global $CONFIG; - - $CONFIG->active_sticky_form = $form_name; -} - -/** * Checks for $array[$key] and returns its value if it exists, else * returns $default. * @@ -1894,12 +1594,22 @@ function elgg_set_active_sticky_form($form_name) { * @param string $key The key to check. * @param array $array The array to check against. * @param mixed $default Default value to return if nothing is found. + * @param bool $strict Return array key if it's set, even if empty. If false, + * return $default if the array key is unset or empty. * - * @return void - * @since 1.8 + * @return mixed + * @since 1.8.0 */ -function elgg_get_array_value($key, array $array, $default = NULL) { - return (isset($array[$key])) ? $array[$key] : $default; +function elgg_extract($key, array $array, $default = null, $strict = true) { + if (!is_array($array)) { + return $default; + } + + if ($strict) { + return (isset($array[$key])) ? $array[$key] : $default; + } else { + return (isset($array[$key]) && !empty($array[$key])) ? $array[$key] : $default; + } } /** @@ -1927,7 +1637,7 @@ $sort_type = SORT_LOCALE_STRING) { $sort = array(); - foreach ($array as $k => $v) { + foreach ($array as $v) { if (isset($v[$element])) { $sort[] = strtolower($v[$element]); } else { @@ -1939,18 +1649,19 @@ $sort_type = SORT_LOCALE_STRING) { } /** - * Return the state of a php.ini setting. + * Return the state of a php.ini setting as a bool * - * Normalizes the setting to bool. + * @warning Using this on ini settings that are not boolean + * will be inaccurate! * * @param string $ini_get_arg The INI setting * - * @return true|false Depending on whether it's on or off + * @return bool Depending on whether it's on or off */ function ini_get_bool($ini_get_arg) { - $temp = ini_get($ini_get_arg); + $temp = strtolower(ini_get($ini_get_arg)); - if ($temp == '1' or strtolower($temp) == 'on') { + if ($temp == '1' || $temp == 'on' || $temp == 'true') { return true; } return false; @@ -1961,7 +1672,7 @@ function ini_get_bool($ini_get_arg) { * * @tip Use this for arithmetic when determining if a file can be uploaded. * - * @param str $setting The php.ini setting + * @param string $setting The php.ini setting * * @return int * @since 1.7.0 @@ -1976,8 +1687,10 @@ function elgg_get_ini_setting_in_bytes($setting) { switch($last) { case 'g': $val *= 1024; + // fallthrough intentional case 'm': $val *= 1024; + // fallthrough intentional case 'k': $val *= 1024; } @@ -2011,10 +1724,11 @@ function is_not_null($string) { * names by singular names. * * @param array $options The options array. $options['keys'] = 'values'; - * @param array $singulars A list of sinular words to pluralize by adding 's'. + * @param array $singulars A list of singular words to pluralize by adding 's'. * * @return array * @since 1.7.0 + * @access private */ function elgg_normalise_plural_options_array($options, $singulars) { foreach ($singulars as $singular) { @@ -2024,7 +1738,12 @@ function elgg_normalise_plural_options_array($options, $singulars) { if ($options[$singular] === ELGG_ENTITIES_ANY_VALUE) { $options[$plural] = $options[$singular]; } else { - $options[$plural] = array($options[$singular]); + // Test for array refs #2641 + if (!is_array($options[$singular])) { + $options[$plural] = array($options[$singular]); + } else { + $options[$plural] = $options[$singular]; + } } } @@ -2035,30 +1754,6 @@ function elgg_normalise_plural_options_array($options, $singulars) { } /** - * Does nothing. - * - * @deprecated 1.7 - * @return 0 - */ -function test_ip() { - elgg_deprecated_notice('test_ip() was removed because of licensing issues.', 1.7); - - return 0; -} - -/** - * Does nothing. - * - * @return bool - * @deprecated 1.7 - */ -function is_ip_in_array() { - elgg_deprecated_notice('is_ip_in_array() was removed because of licensing issues.', 1.7); - - return false; -} - -/** * Emits a shutdown:system event upon PHP shutdown, but before database connections are dropped. * * @tip Register for the shutdown:system event to perform functions at the end of page loads. @@ -2067,17 +1762,27 @@ function is_ip_in_array() { * useful. Servers will hold pages until processing is done before sending * them out to the browser. * + * @see http://www.php.net/register-shutdown-function + * * @return void * @see register_shutdown_hook() + * @access private */ function _elgg_shutdown_hook() { global $START_MICROTIME; - trigger_elgg_event('shutdown', 'system'); + try { + elgg_trigger_event('shutdown', 'system'); - $time = (float)(microtime(TRUE) - $START_MICROTIME); - // demoted to NOTICE from DEBUG so javascript is not corrupted - elgg_log("Page {$_SERVER['REQUEST_URI']} generated in $time seconds", 'NOTICE'); + $time = (float)(microtime(TRUE) - $START_MICROTIME); + // demoted to NOTICE from DEBUG so javascript is not corrupted + elgg_log("Page {$_SERVER['REQUEST_URI']} generated in $time seconds", 'NOTICE'); + } catch (Exception $e) { + $message = 'Error: ' . get_class($e) . ' thrown within the shutdown handler. '; + $message .= "Message: '{$e->getMessage()}' in file {$e->getFile()} (line {$e->getLine()})"; + error_log($message); + error_log("Exception trace stack: {$e->getTraceAsString()}"); + } } /** @@ -2088,23 +1793,251 @@ function _elgg_shutdown_hook() { * * @param array $page The page array * - * @return void + * @return bool * @elgg_pagehandler js + * @access private + */ +function elgg_js_page_handler($page) { + return elgg_cacheable_view_page_handler($page, 'js'); +} + +/** + * Serve individual views for Ajax. + * + * /ajax/view/<name of view>?<key/value params> + * + * @param array $page The page array + * + * @return bool + * @elgg_pagehandler ajax + * @access private */ -function js_page_handler($page) { +function elgg_ajax_page_handler($page) { if (is_array($page) && sizeof($page)) { - $js = str_replace('.js', '', $page[0]); - $return = elgg_view('js/' . $js); + // throw away 'view' and form the view name + unset($page[0]); + $view = implode('/', $page); + + $allowed_views = elgg_get_config('allowed_ajax_views'); + if (!array_key_exists($view, $allowed_views)) { + header('HTTP/1.1 403 Forbidden'); + exit; + } + + // pull out GET parameters through filter + $vars = array(); + foreach ($_GET as $name => $value) { + $vars[$name] = get_input($name); + } + + if (isset($vars['guid'])) { + $vars['entity'] = get_entity($vars['guid']); + } + + echo elgg_view($view, $vars); + return true; + } + return false; +} + +/** + * Serve CSS + * + * Serves CSS from the css views directory with headers for caching control + * + * @param array $page The page array + * + * @return bool + * @elgg_pagehandler css + * @access private + */ +function elgg_css_page_handler($page) { + if (!isset($page[0])) { + // default css + $page[0] = 'elgg'; + } + + return elgg_cacheable_view_page_handler($page, 'css'); +} + +/** + * Serves a JS or CSS view with headers for caching. + * + * /<css||js>/name/of/view.<last_cache>.<css||js> + * + * @param array $page The page array + * @param string $type The type: js or css + * + * @return bool + * @access private + */ +function elgg_cacheable_view_page_handler($page, $type) { + + switch ($type) { + case 'js': + $content_type = 'text/javascript'; + break; + + case 'css': + $content_type = 'text/css'; + break; - header('Content-type: text/javascript'); - header('Expires: ' . date('r', time() + 864000)); - header("Pragma: public"); - header("Cache-Control: public"); - header("Content-Length: " . strlen($return)); + default: + return false; + break; + } + + if ($page) { + // the view file names can have multiple dots + // eg: views/default/js/calendars/jquery.fullcalendar.min.php + // translates to the url /js/calendars/jquery.fullcalendar.min.<ts>.js + // and the view js/calendars/jquery.fullcalendar.min + // we ignore the last two dots for the ts and the ext. + // Additionally, the timestamp is optional. + $page = implode('/', $page); + $regex = '|(.+?)\.([\d]+\.)?\w+$|'; + preg_match($regex, $page, $matches); + $view = $matches[1]; + $return = elgg_view("$type/$view"); + + header("Content-type: $content_type"); + + // @todo should js be cached when simple cache turned off + //header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', strtotime("+10 days")), true); + //header("Pragma: public"); + //header("Cache-Control: public"); + //header("Content-Length: " . strlen($return)); echo $return; - exit; + return true; + } + return false; +} + +/** + * Reverses the ordering in an ORDER BY clause. This is achived by replacing + * asc with desc, or appending desc to the end of the clause. + * + * This is used mostly for elgg_get_entities() and other similar functions. + * + * @param string $order_by An order by clause + * @access private + * @return string + * @access private + */ +function elgg_sql_reverse_order_by_clause($order_by) { + $order_by = strtolower($order_by); + + if (strpos($order_by, ' asc') !== false) { + $return = str_replace(' asc', ' desc', $order_by); + } elseif (strpos($order_by, ' desc') !== false) { + $return = str_replace(' desc', ' asc', $order_by); + } else { + // no order specified, so default to desc since mysql defaults to asc + $return = $order_by . ' desc'; + } + + return $return; +} + +/** + * Enable objects with an enable() method. + * + * Used as a callback for ElggBatch. + * + * @todo why aren't these static methods on ElggBatch? + * + * @param object $object The object to enable + * @return bool + * @access private + */ +function elgg_batch_enable_callback($object) { + // our db functions return the number of rows affected... + return $object->enable() ? true : false; +} + +/** + * Disable objects with a disable() method. + * + * Used as a callback for ElggBatch. + * + * @param object $object The object to disable + * @return bool + * @access private + */ +function elgg_batch_disable_callback($object) { + // our db functions return the number of rows affected... + return $object->disable() ? true : false; +} + +/** + * Delete objects with a delete() method. + * + * Used as a callback for ElggBatch. + * + * @param object $object The object to disable + * @return bool + * @access private + */ +function elgg_batch_delete_callback($object) { + // our db functions return the number of rows affected... + return $object->delete() ? true : false; +} + +/** + * Checks if there are some constraints on the options array for + * potentially dangerous operations. + * + * @param array $options Options array + * @param string $type Options type: metadata or annotations + * @return bool + * @access private + */ +function elgg_is_valid_options_for_batch_operation($options, $type) { + if (!$options || !is_array($options)) { + return false; } + + // at least one of these is required. + $required = array( + // generic restraints + 'guid', 'guids' + ); + + switch ($type) { + case 'metadata': + $metadata_required = array( + 'metadata_owner_guid', 'metadata_owner_guids', + 'metadata_name', 'metadata_names', + 'metadata_value', 'metadata_values' + ); + + $required = array_merge($required, $metadata_required); + break; + + case 'annotations': + case 'annotation': + $annotations_required = array( + 'annotation_owner_guid', 'annotation_owner_guids', + 'annotation_name', 'annotation_names', + 'annotation_value', 'annotation_values' + ); + + $required = array_merge($required, $annotations_required); + break; + + default: + return false; + } + + foreach ($required as $key) { + // check that it exists and is something. + if (isset($options[$key]) && $options[$key]) { + return true; + } + } + + return false; } /** @@ -2112,15 +2045,53 @@ function js_page_handler($page) { * * @link http://docs.elgg.org/Tutorials/WalledGarden * @elgg_plugin_hook index system - * @return void + * + * @param string $hook The name of the hook + * @param string $type The type of hook + * @param bool $value Has a plugin already rendered an index page? + * @param array $params Array of parameters (should be empty) + * @return bool + * @access private */ -function elgg_walled_garden_index() { - $login = elgg_view('account/forms/login_walled_garden'); +function elgg_walled_garden_index($hook, $type, $value, $params) { + if ($value) { + // do not create a second index page so return + return; + } + + elgg_load_css('elgg.walled_garden'); + elgg_load_js('elgg.walled_garden'); + + $content = elgg_view('core/walled_garden/login'); + + $params = array( + 'content' => $content, + 'class' => 'elgg-walledgarden-double', + 'id' => 'elgg-walledgarden-login', + ); + $body = elgg_view_layout('walled_garden', $params); + echo elgg_view_page('', $body, 'walled_garden'); - echo elgg_view_page('', $login, 'page_shells/walled_garden'); + // return true to prevent other plugins from adding a front page + return true; +} - // @hack Index must exit to keep plugins from continuing to extend - exit; +/** + * Serve walled garden sections + * + * @param array $page Array of URL segments + * @return string + * @access private + */ +function _elgg_walled_garden_ajax_handler($page) { + $view = $page[0]; + $params = array( + 'content' => elgg_view("core/walled_garden/$view"), + 'class' => 'elgg-walledgarden-single hidden', + 'id' => str_replace('_', '-', "elgg-walledgarden-$view"), + ); + echo elgg_view_layout('walled_garden', $params); + return true; } /** @@ -2131,14 +2102,20 @@ function elgg_walled_garden_index() { * plugin pages by {@elgg_hook public_pages walled_garden} will redirect to * a login page. * - * @since 1.8 + * @since 1.8.0 * @elgg_event_handler init system * @link http://docs.elgg.org/Tutorials/WalledGarden * @return void + * @access private */ function elgg_walled_garden() { global $CONFIG; + elgg_register_css('elgg.walled_garden', '/css/walled_garden.css'); + elgg_register_js('elgg.walled_garden', '/js/walled_garden.js'); + + elgg_register_page_handler('walled_garden', '_elgg_walled_garden_ajax_handler'); + // check for external page view if (isset($CONFIG->site) && $CONFIG->site instanceof ElggSite) { $CONFIG->site->checkWalledGarden(); @@ -2146,26 +2123,95 @@ function elgg_walled_garden() { } /** + * Remove public access for walled gardens + * + * @param string $hook + * @param string $type + * @param array $accesses + * @return array + * @access private + */ +function _elgg_walled_garden_remove_public_access($hook, $type, $accesses) { + if (isset($accesses[ACCESS_PUBLIC])) { + unset($accesses[ACCESS_PUBLIC]); + } + return $accesses; +} + +/** + * Boots the engine + * + * 1. sets error handlers + * 2. connects to database + * 3. verifies the installation suceeded + * 4. loads application configuration + * 5. loads i18n data + * 6. loads site configuration + * + * @access private + */ +function _elgg_engine_boot() { + // Register the error handlers + set_error_handler('_elgg_php_error_handler'); + set_exception_handler('_elgg_php_exception_handler'); + + setup_db_connections(); + + verify_installation(); + + _elgg_load_application_config(); + + _elgg_load_site_config(); + + _elgg_session_boot(); + + _elgg_load_cache(); + + _elgg_load_translations(); +} + +/** * Elgg's main init. * - * Handles core actions for comments and likes, the JS pagehandler, and the shutdown function. + * Handles core actions for comments, the JS pagehandler, and the shutdown function. * * @elgg_event_handler init system * @return void + * @access private */ function elgg_init() { global $CONFIG; - register_action('comments/add'); - register_action('comments/delete'); - register_action('likes/add'); - register_action('likes/delete'); + elgg_register_action('comments/add'); + elgg_register_action('comments/delete'); - register_page_handler('js', 'js_page_handler'); + elgg_register_page_handler('js', 'elgg_js_page_handler'); + elgg_register_page_handler('css', 'elgg_css_page_handler'); + elgg_register_page_handler('ajax', 'elgg_ajax_page_handler'); + elgg_register_js('elgg.autocomplete', 'js/lib/ui.autocomplete.js'); + elgg_register_js('jquery.ui.autocomplete.html', 'vendors/jquery/jquery.ui.autocomplete.html.js'); + elgg_register_js('elgg.userpicker', 'js/lib/ui.userpicker.js'); + elgg_register_js('elgg.friendspicker', 'js/lib/ui.friends_picker.js'); + elgg_register_js('jquery.easing', 'vendors/jquery/jquery.easing.1.3.packed.js'); + elgg_register_js('elgg.avatar_cropper', 'js/lib/ui.avatar_cropper.js'); + elgg_register_js('jquery.imgareaselect', 'vendors/jquery/jquery.imgareaselect-0.9.8/scripts/jquery.imgareaselect.min.js'); + elgg_register_js('elgg.ui.river', 'js/lib/ui.river.js'); + + elgg_register_css('jquery.imgareaselect', 'vendors/jquery/jquery.imgareaselect-0.9.8/css/imgareaselect-deprecated.css'); + // Trigger the shutdown:system event upon PHP shutdown. register_shutdown_function('_elgg_shutdown_hook'); + $logo_url = elgg_get_site_url() . "_graphics/elgg_toolbar_logo.gif"; + elgg_register_menu_item('topbar', array( + 'name' => 'elgg_logo', + 'href' => 'http://www.elgg.org/', + 'text' => "<img src=\"$logo_url\" alt=\"Elgg logo\" width=\"38\" height=\"20\" />", + 'priority' => 1, + 'link_class' => 'elgg-topbar-logo', + )); + // Sets a blacklist of words in the current language. // This is a comma separated list in word:blacklist. // @todo possibly deprecate @@ -2187,7 +2233,8 @@ function elgg_init() { * @param array $params empty * * @elgg_plugin_hook unit_tests system - * @return void + * @return array + * @access private */ function elgg_api_test($hook, $type, $value, $params) { global $CONFIG; @@ -2198,7 +2245,10 @@ function elgg_api_test($hook, $type, $value, $params) { } /**#@+ - * Controlls access levels on ElggEntity entities, metadata, and annotations. + * Controls access levels on ElggEntity entities, metadata, and annotations. + * + * @warning ACCESS_DEFAULT is a place holder for the input/access view. Do not + * use it when saving an entity. * * @var int */ @@ -2232,7 +2282,7 @@ define('ELGG_ENTITIES_NO_VALUE', 0); * referring page. * * @see forward - * @var unknown_type + * @var int -1 */ define('REFERRER', -1); @@ -2246,8 +2296,9 @@ define('REFERRER', -1); */ define('REFERER', -1); -register_elgg_event_handler('init', 'system', 'elgg_init'); -register_plugin_hook('unit_test', 'system', 'elgg_api_test'); +elgg_register_event_handler('init', 'system', 'elgg_init'); +elgg_register_event_handler('boot', 'system', '_elgg_engine_boot', 1); +elgg_register_plugin_hook_handler('unit_test', 'system', 'elgg_api_test'); -register_elgg_event_handler('init', 'system', 'add_custom_menu_items', 1000); -register_elgg_event_handler('init', 'system', 'elgg_walled_garden', 1000); +elgg_register_event_handler('init', 'system', 'add_custom_menu_items', 1000); +elgg_register_event_handler('init', 'system', 'elgg_walled_garden', 1000); diff --git a/engine/lib/entities.php b/engine/lib/entities.php index 4a971eb57..4fcf1c657 100644 --- a/engine/lib/entities.php +++ b/engine/lib/entities.php @@ -13,29 +13,53 @@ * @global array $ENTITY_CACHE * @access private */ -$ENTITY_CACHE = NULL; +global $ENTITY_CACHE; +$ENTITY_CACHE = array(); /** - * Cache subtypes and related class names once loaded. + * GUIDs of entities banned from the entity cache (during this request) * - * @global array $SUBTYPE_CACHE + * @global array $ENTITY_CACHE_DISABLED_GUIDS * @access private */ -$SUBTYPE_CACHE = NULL; +global $ENTITY_CACHE_DISABLED_GUIDS; +$ENTITY_CACHE_DISABLED_GUIDS = array(); /** - * Initialise the entity cache. + * Cache subtypes and related class names. * - * @return void - * @todo remove this. + * @global array|null $SUBTYPE_CACHE array once populated from DB, initially null * @access private */ -function initialise_entity_cache() { - global $ENTITY_CACHE; +global $SUBTYPE_CACHE; +$SUBTYPE_CACHE = null; - if (!$ENTITY_CACHE) { - $ENTITY_CACHE = array(); - } +/** + * Remove this entity from the entity cache and make sure it is not re-added + * + * @param int $guid The entity guid + * + * @access private + * @todo this is a workaround until #5604 can be implemented + */ +function _elgg_disable_caching_for_entity($guid) { + global $ENTITY_CACHE_DISABLED_GUIDS; + + _elgg_invalidate_cache_for_entity($guid); + $ENTITY_CACHE_DISABLED_GUIDS[$guid] = true; +} + +/** + * Allow this entity to be stored in the entity cache + * + * @param int $guid The entity guid + * + * @access private + */ +function _elgg_enable_caching_for_entity($guid) { + global $ENTITY_CACHE_DISABLED_GUIDS; + + unset($ENTITY_CACHE_DISABLED_GUIDS[$guid]); } /** @@ -46,12 +70,14 @@ function initialise_entity_cache() { * @return void * @access private */ -function invalidate_cache_for_entity($guid) { +function _elgg_invalidate_cache_for_entity($guid) { global $ENTITY_CACHE; $guid = (int)$guid; unset($ENTITY_CACHE[$guid]); + + elgg_get_metadata_cache()->clear($guid); } /** @@ -62,60 +88,59 @@ function invalidate_cache_for_entity($guid) { * @param ElggEntity $entity Entity to cache * * @return void - * @see retrieve_cached_entity() - * @see invalidate_cache_for_entity() + * @see _elgg_retrieve_cached_entity() + * @see _elgg_invalidate_cache_for_entity() * @access private + * @todo Use an ElggCache object */ -function cache_entity(ElggEntity $entity) { - global $ENTITY_CACHE; +function _elgg_cache_entity(ElggEntity $entity) { + global $ENTITY_CACHE, $ENTITY_CACHE_DISABLED_GUIDS; - $ENTITY_CACHE[$entity->guid] = $entity; -} + // Don't cache non-plugin entities while access control is off, otherwise they could be + // exposed to users who shouldn't see them when control is re-enabled. + if (!($entity instanceof ElggPlugin) && elgg_get_ignore_access()) { + return; + } -/** - * Retrieve a entity from the cache. - * - * @param int $guid The guid - * - * @return void - * @see cache_entity() - * @see invalidate_cache_for_entity() - * @access private - */ -function retrieve_cached_entity($guid) { - global $ENTITY_CACHE; + $guid = $entity->getGUID(); + if (isset($ENTITY_CACHE_DISABLED_GUIDS[$guid])) { + return; + } - $guid = (int)$guid; + // Don't store too many or we'll have memory problems + // @todo Pick a less arbitrary limit + if (count($ENTITY_CACHE) > 256) { + $random_guid = array_rand($ENTITY_CACHE); - if (isset($ENTITY_CACHE[$guid])) { - if ($ENTITY_CACHE[$guid]->isFullyLoaded()) { - return $ENTITY_CACHE[$guid]; - } + unset($ENTITY_CACHE[$random_guid]); + + // Purge separate metadata cache. Original idea was to do in entity destructor, but that would + // have caused a bunch of unnecessary purges at every shutdown. Doing it this way we have no way + // to know that the expunged entity will be GCed (might be another reference living), but that's + // OK; the metadata will reload if necessary. + elgg_get_metadata_cache()->clear($random_guid); } - return false; + $ENTITY_CACHE[$guid] = $entity; } /** - * As retrieve_cached_entity, but returns the result as a stdClass - * (compatible with load functions that expect a database row.) + * Retrieve a entity from the cache. * * @param int $guid The guid * - * @return mixed - * @todo unused + * @return ElggEntity|bool false if entity not cached, or not fully loaded + * @see _elgg_cache_entity() + * @see _elgg_invalidate_cache_for_entity() * @access private */ -function retrieve_cached_entity_row($guid) { - $obj = retrieve_cached_entity($guid); - if ($obj) { - $tmp = new stdClass; +function _elgg_retrieve_cached_entity($guid) { + global $ENTITY_CACHE; - foreach ($obj as $k => $v) { - $tmp->$k = $v; + if (isset($ENTITY_CACHE[$guid])) { + if ($ENTITY_CACHE[$guid]->isFullyLoaded()) { + return $ENTITY_CACHE[$guid]; } - - return $tmp; } return false; @@ -137,8 +162,6 @@ function retrieve_cached_entity_row($guid) { * @internal Subtypes are stored in the entity_subtypes table. There is a foreign * key in the entities table. * - * @todo Move to a nicer place? - * * @param string $type Type * @param string $subtype Subtype * @@ -148,75 +171,99 @@ function retrieve_cached_entity_row($guid) { * @access private */ function get_subtype_id($type, $subtype) { - global $CONFIG, $SUBTYPE_CACHE; - - $type = sanitise_string($type); - $subtype = sanitise_string($subtype); + global $SUBTYPE_CACHE; - if ($subtype == "") { - return FALSE; + if (!$subtype) { + return false; } - // Todo: cache here? Or is looping less efficient that going to the db each time? - $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes - where type='$type' and subtype='$subtype'"); - - if ($result) { - if (!$SUBTYPE_CACHE) { - $SUBTYPE_CACHE = array(); - } + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } - $SUBTYPE_CACHE[$result->id] = $result; + // use the cache before hitting database + $result = _elgg_retrieve_cached_subtype($type, $subtype); + if ($result !== null) { return $result->id; } - return FALSE; + return false; } /** * Return string name for a given subtype ID. * - * @todo Move to a nicer place? - * * @param int $subtype_id Subtype ID * - * @return string Subtype name + * @return string|false Subtype name, false if subtype not found * @link http://docs.elgg.org/DataModel/Entities/Subtypes * @see get_subtype_from_id() * @access private */ function get_subtype_from_id($subtype_id) { - global $CONFIG, $SUBTYPE_CACHE; - - $subtype_id = (int)$subtype_id; + global $SUBTYPE_CACHE; if (!$subtype_id) { return false; } + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + if (isset($SUBTYPE_CACHE[$subtype_id])) { return $SUBTYPE_CACHE[$subtype_id]->subtype; } - $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes where id=$subtype_id"); - if ($result) { - if (!$SUBTYPE_CACHE) { - $SUBTYPE_CACHE = array(); - } + return false; +} - $SUBTYPE_CACHE[$subtype_id] = $result; - return $result->subtype; +/** + * Retrieve subtype from the cache. + * + * @param string $type + * @param string $subtype + * @return stdClass|null + * + * @access private + */ +function _elgg_retrieve_cached_subtype($type, $subtype) { + global $SUBTYPE_CACHE; + + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); } - return false; + foreach ($SUBTYPE_CACHE as $obj) { + if ($obj->type === $type && $obj->subtype === $subtype) { + return $obj; + } + } + return null; +} + +/** + * Fetch all suptypes from DB to local cache. + * + * @access private + */ +function _elgg_populate_subtype_cache() { + global $CONFIG, $SUBTYPE_CACHE; + + $results = get_data("SELECT * FROM {$CONFIG->dbprefix}entity_subtypes"); + + $SUBTYPE_CACHE = array(); + foreach ($results as $row) { + $SUBTYPE_CACHE[$row->id] = $row; + } } /** - * Return a classname for a registered type and subtype. + * Return the class name for a registered type and subtype. * * Entities can be registered to always be loaded as a certain class - * with {@link register_entity_subtype()}. This function returns - * the class name if found, and NULL if not. + * with add_subtype() or update_subtype(). This function returns the class + * name if found and NULL if not. * * @param string $type The type * @param string $subtype The subtype @@ -227,29 +274,23 @@ function get_subtype_from_id($subtype_id) { * @access private */ function get_subtype_class($type, $subtype) { - global $CONFIG, $SUBTYPE_CACHE; - - $type = sanitise_string($type); - $subtype = sanitise_string($subtype); + global $SUBTYPE_CACHE; - // Todo: cache here? Or is looping less efficient that going to the db each time? - $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes - where type='$type' and subtype='$subtype'"); - - if ($result) { - if (!$SUBTYPE_CACHE) { - $SUBTYPE_CACHE = array(); - } - - $SUBTYPE_CACHE[$result->id] = $result; - return $result->class; + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + + // use the cache before going to the database + $obj = _elgg_retrieve_cached_subtype($type, $subtype); + if ($obj) { + return $obj->class; } - return NULL; + return null; } /** - * Returns the classname for a subtype id. + * Returns the class name for a subtype id. * * @param int $subtype_id The subtype id * @@ -259,29 +300,21 @@ function get_subtype_class($type, $subtype) { * @access private */ function get_subtype_class_from_id($subtype_id) { - global $CONFIG, $SUBTYPE_CACHE; - - $subtype_id = (int)$subtype_id; + global $SUBTYPE_CACHE; if (!$subtype_id) { - return false; + return null; } + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); + } + if (isset($SUBTYPE_CACHE[$subtype_id])) { return $SUBTYPE_CACHE[$subtype_id]->class; } - $result = get_data_row("SELECT * from {$CONFIG->dbprefix}entity_subtypes where id=$subtype_id"); - - if ($result) { - if (!$SUBTYPE_CACHE) { - $SUBTYPE_CACHE = array(); - } - $SUBTYPE_CACHE[$subtype_id] = $result; - return $result->class; - } - - return NULL; + return null; } /** @@ -292,6 +325,9 @@ function get_subtype_class_from_id($subtype_id) { * it will be loaded as that class automatically when retrieved from the database with * {@link get_entity()}. * + * @warning This function cannot be used to change the class for a type-subtype pair. + * Use update_subtype() for that. + * * @param string $type The type you're subtyping (site, user, object, or group) * @param string $subtype The subtype * @param string $class Optional class name for the object @@ -304,21 +340,32 @@ function get_subtype_class_from_id($subtype_id) { * @see get_entity() */ function add_subtype($type, $subtype, $class = "") { - global $CONFIG; - $type = sanitise_string($type); - $subtype = sanitise_string($subtype); - $class = sanitise_string($class); + global $CONFIG, $SUBTYPE_CACHE; - // Short circuit if no subtype is given - if ($subtype == "") { + if (!$subtype) { return 0; } $id = get_subtype_id($type, $subtype); - if ($id == 0) { - return insert_data("insert into {$CONFIG->dbprefix}entity_subtypes" - . " (type, subtype, class) values ('$type','$subtype','$class')"); + if (!$id) { + // In cache we store non-SQL-escaped strings because that's what's returned by query + $cache_obj = (object) array( + 'type' => $type, + 'subtype' => $subtype, + 'class' => $class, + ); + + $type = sanitise_string($type); + $subtype = sanitise_string($subtype); + $class = sanitise_string($class); + + $id = insert_data("INSERT INTO {$CONFIG->dbprefix}entity_subtypes" + . " (type, subtype, class) VALUES ('$type', '$subtype', '$class')"); + + // add entry to cache + $cache_obj->id = $id; + $SUBTYPE_CACHE[$id] = $cache_obj; } return $id; @@ -327,6 +374,10 @@ function add_subtype($type, $subtype, $class = "") { /** * Removes a registered ElggEntity type, subtype, and classname. * + * @warning You do not want to use this function. If you want to unregister + * a class for a subtype, use update_subtype(). Using this function will + * permanently orphan all the objects created with the specified subtype. + * * @param string $type Type * @param string $subtype Subtype * @@ -345,7 +396,7 @@ function remove_subtype($type, $subtype) { } /** - * Update a registered ElggEntity type, subtype, and classname + * Update a registered ElggEntity type, subtype, and class name * * @param string $type Type * @param string $subtype Subtype @@ -354,18 +405,33 @@ function remove_subtype($type, $subtype) { * @return bool */ function update_subtype($type, $subtype, $class = '') { - global $CONFIG; + global $CONFIG, $SUBTYPE_CACHE; - if (!$id = get_subtype_id($type, $subtype)) { - return FALSE; + $id = get_subtype_id($type, $subtype); + if (!$id) { + return false; + } + + if ($SUBTYPE_CACHE === null) { + _elgg_populate_subtype_cache(); } + + $unescaped_class = $class; + $type = sanitise_string($type); $subtype = sanitise_string($subtype); - - return update_data("UPDATE {$CONFIG->dbprefix}entity_subtypes + $class = sanitise_string($class); + + $success = update_data("UPDATE {$CONFIG->dbprefix}entity_subtypes SET type = '$type', subtype = '$subtype', class = '$class' WHERE id = $id "); + + if ($success && isset($SUBTYPE_CACHE[$id])) { + $SUBTYPE_CACHE[$id]->class = $unescaped_class; + } + + return $success; } /** @@ -380,12 +446,13 @@ function update_subtype($type, $subtype, $class = '') { * @param int $owner_guid The new owner guid * @param int $access_id The new access id * @param int $container_guid The new container guid + * @param int $time_created The time creation timestamp * * @return bool - * @link http://docs.elgg.org/DataModel/Entities + * @throws InvalidParameterException * @access private */ -function update_entity($guid, $owner_guid, $access_id, $container_guid = null) { +function update_entity($guid, $owner_guid, $access_id, $container_guid = null, $time_created = null) { global $CONFIG, $ENTITY_CACHE; $guid = (int)$guid; @@ -399,11 +466,22 @@ function update_entity($guid, $owner_guid, $access_id, $container_guid = null) { $entity = get_entity($guid); + if ($time_created == null) { + $time_created = $entity->time_created; + } else { + $time_created = (int) $time_created; + } + + if ($access_id == ACCESS_DEFAULT) { + throw new InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h'); + } + if ($entity && $entity->canEdit()) { - if (trigger_elgg_event('update', $entity->type, $entity)) { - $ret = update_data("UPDATE {$CONFIG->dbprefix}entities" - . " set owner_guid='$owner_guid', access_id='$access_id'," - . " container_guid='$container_guid', time_updated='$time' WHERE guid=$guid"); + if (elgg_trigger_event('update', $entity->type, $entity)) { + $ret = update_data("UPDATE {$CONFIG->dbprefix}entities + set owner_guid='$owner_guid', access_id='$access_id', + container_guid='$container_guid', time_created='$time_created', + time_updated='$time' WHERE guid=$guid"); if ($entity instanceof ElggObject) { update_river_access_by_object($guid, $access_id); @@ -415,7 +493,7 @@ function update_entity($guid, $owner_guid, $access_id, $container_guid = null) { $newentity_cache = new ElggMemcache('new_entity_cache'); } if ($newentity_cache) { - $new_entity = $newentity_cache->delete($guid); + $newentity_cache->delete($guid); } // Handle cases where there was no error BUT no rows were updated! @@ -429,7 +507,7 @@ function update_entity($guid, $owner_guid, $access_id, $container_guid = null) { } /** - * Determine if a given user is can write to an entity container. + * Determine if a given user can write to an entity container. * * An entity can be a container for any other entity by setting the * container_guid. container_guid can differ from owner_guid. @@ -437,20 +515,19 @@ function update_entity($guid, $owner_guid, $access_id, $container_guid = null) { * A plugin hook container_permissions_check:$entity_type is emitted to allow granular * access controls in plugins. * - * @param int $user_guid The user guid, or 0 for get_loggedin_userid() + * @param int $user_guid The user guid, or 0 for logged in user * @param int $container_guid The container, or 0 for the current page owner. - * @param string $entity_type The type of entities. Defauts to 'all' + * @param string $type The type of entity we're looking to write + * @param string $subtype The subtype of the entity we're looking to write * * @return bool * @link http://docs.elgg.org/DataModel/Containers */ -function can_write_to_container($user_guid = 0, $container_guid = 0, $entity_type = 'all') { - global $CONFIG; - +function can_write_to_container($user_guid = 0, $container_guid = 0, $type = 'all', $subtype = 'all') { $user_guid = (int)$user_guid; $user = get_entity($user_guid); if (!$user) { - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); } $container_guid = (int)$container_guid; @@ -458,8 +535,10 @@ function can_write_to_container($user_guid = 0, $container_guid = 0, $entity_typ $container_guid = elgg_get_page_owner_guid(); } + $return = false; + if (!$container_guid) { - $return = TRUE; + $return = true; } $container = get_entity($container_guid); @@ -467,23 +546,29 @@ function can_write_to_container($user_guid = 0, $container_guid = 0, $entity_typ if ($container) { // If the user can edit the container, they can also write to it if ($container->canEdit($user_guid)) { - $return = TRUE; + $return = true; } - // Basics, see if the user is a member of the group. - // @todo this should be moved to the groups plugin - if ($user && $container instanceof ElggGroup) { - if (!$container->isMember($user)) { - $return = FALSE; - } else { - $return = TRUE; + // If still not approved, see if the user is a member of the group + // @todo this should be moved to the groups plugin/library + if (!$return && $user && $container instanceof ElggGroup) { + /* @var ElggGroup $container */ + if ($container->isMember($user)) { + $return = true; } } } // See if anyone else has anything to say - return trigger_plugin_hook('container_permissions_check', $entity_type, - array('container' => $container, 'user' => $user), $return); + return elgg_trigger_plugin_hook( + 'container_permissions_check', + $type, + array( + 'container' => $container, + 'user' => $user, + 'subtype' => $subtype + ), + $return); } /** @@ -506,8 +591,8 @@ function can_write_to_container($user_guid = 0, $container_guid = 0, $entity_typ * * @return int|false The new entity's GUID, or false on failure * @throws InvalidParameterException - * @access private * @link http://docs.elgg.org/DataModel/Entities + * @access private */ function create_entity($type, $subtype, $owner_guid, $access_id, $site_guid = 0, $container_guid = 0) { @@ -515,9 +600,8 @@ $container_guid = 0) { global $CONFIG; $type = sanitise_string($type); - $subtype = add_subtype($type, $subtype); + $subtype_id = add_subtype($type, $subtype); $owner_guid = (int)$owner_guid; - $access_id = (int)$access_id; $time = time(); if ($site_guid == 0) { $site_guid = $CONFIG->site_guid; @@ -526,13 +610,17 @@ $container_guid = 0) { if ($container_guid == 0) { $container_guid = $owner_guid; } + $access_id = (int)$access_id; + if ($access_id == ACCESS_DEFAULT) { + throw new InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h'); + } - $user = get_loggedin_user(); - if (!can_write_to_container($user->guid, $owner_guid, $type)) { + $user_guid = elgg_get_logged_in_user_guid(); + if (!can_write_to_container($user_guid, $owner_guid, $type, $subtype)) { return false; } if ($owner_guid != $container_guid) { - if (!can_write_to_container($user->guid, $container_guid, $type)) { + if (!can_write_to_container($user_guid, $container_guid, $type, $subtype)) { return false; } } @@ -544,7 +632,7 @@ $container_guid = 0) { (type, subtype, owner_guid, site_guid, container_guid, access_id, time_created, time_updated, last_action) values - ('$type',$subtype, $owner_guid, $site_guid, $container_guid, + ('$type',$subtype_id, $owner_guid, $site_guid, $container_guid, $access_id, $time, $time, $time)"); } @@ -583,12 +671,14 @@ function get_entity_as_row($guid) { * * @param stdClass $row The row of the entry in the entities table. * - * @return object|false + * @return ElggEntity|false * @link http://docs.elgg.org/DataModel/Entities * @see get_entity_as_row() * @see add_subtype() * @see get_entity() * @access private + * + * @throws ClassException|InstallationException */ function entity_row_to_elggstar($row) { if (!($row instanceof stdClass)) { @@ -620,11 +710,11 @@ function entity_row_to_elggstar($row) { $new_entity = new $classname($row); if (!($new_entity instanceof ElggEntity)) { - $msg = sprintf(elgg_echo('ClassException:ClassnameNotClass'), $classname, 'ElggEntity'); + $msg = elgg_echo('ClassException:ClassnameNotClass', array($classname, 'ElggEntity')); throw new ClassException($msg); } } else { - error_log(sprintf(elgg_echo('ClassNotFoundException:MissingClass'), $classname)); + error_log(elgg_echo('ClassNotFoundException:MissingClass', array($classname))); } } @@ -644,7 +734,7 @@ function entity_row_to_elggstar($row) { $new_entity = new ElggSite($row); break; default: - $msg = sprintf(elgg_echo('InstallationException:TypeNotSupported'), $row->type); + $msg = elgg_echo('InstallationException:TypeNotSupported', array($row->type)); throw new InstallationException($msg); } } @@ -662,32 +752,91 @@ function entity_row_to_elggstar($row) { * * @param int $guid The GUID of the entity * - * @return object The correct Elgg or custom object based upon entity type and subtype + * @return ElggEntity The correct Elgg or custom object based upon entity type and subtype * @link http://docs.elgg.org/DataModel/Entities */ function get_entity($guid) { - static $newentity_cache; - $new_entity = false; + // This should not be a static local var. Notice that cache writing occurs in a completely + // different instance outside this function. + // @todo We need a single Memcache instance with a shared pool of namespace wrappers. This function would pull an instance from the pool. + static $shared_cache; + + // We could also use: if (!(int) $guid) { return FALSE }, + // but that evaluates to a false positive for $guid = TRUE. + // This is a bit slower, but more thorough. + if (!is_numeric($guid) || $guid === 0 || $guid === '0') { + return false; + } + + // Check local cache first + $new_entity = _elgg_retrieve_cached_entity($guid); + if ($new_entity) { + return $new_entity; + } - if (!is_numeric($guid)) { - return FALSE; + // Check shared memory cache, if available + if (null === $shared_cache) { + if (is_memcache_available()) { + $shared_cache = new ElggMemcache('new_entity_cache'); + } else { + $shared_cache = false; + } } - if ((!$newentity_cache) && (is_memcache_available())) { - $newentity_cache = new ElggMemcache('new_entity_cache'); + // until ACLs in memcache, DB query is required to determine access + $entity_row = get_entity_as_row($guid); + if (!$entity_row) { + return false; } - if ($newentity_cache) { - $new_entity = $newentity_cache->load($guid); + if ($shared_cache) { + $cached_entity = $shared_cache->load($guid); + // @todo store ACLs in memcache https://github.com/elgg/elgg/issues/3018#issuecomment-13662617 + if ($cached_entity) { + // @todo use ACL and cached entity access_id to determine if user can see it + return $cached_entity; + } } - if ($new_entity) { - return $new_entity; + // don't let incomplete entities cause fatal exceptions + try { + $new_entity = entity_row_to_elggstar($entity_row); + } catch (IncompleteEntityException $e) { + return false; } - return entity_row_to_elggstar(get_entity_as_row($guid)); + if ($new_entity) { + _elgg_cache_entity($new_entity); + } + return $new_entity; } +/** + * Does an entity exist? + * + * This function checks for the existence of an entity independent of access + * permissions. It is useful for situations when a user cannot access an entity + * and it must be determined whether entity has been deleted or the access level + * has changed. + * + * @param int $guid The GUID of the entity + * + * @return bool + * @since 1.8.0 + */ +function elgg_entity_exists($guid) { + global $CONFIG; + + $guid = sanitize_int($guid); + + $query = "SELECT count(*) as total FROM {$CONFIG->dbprefix}entities WHERE guid = $guid"; + $result = get_data_row($query); + if ($result->total == 0) { + return false; + } else { + return true; + } +} /** * Returns an array of entities with optional filtering. @@ -709,19 +858,24 @@ function get_entity($guid) { * Joined with subtypes by AND. See below) * * subtypes => NULL|STR entity subtype (SQL: subtype IN ('subtype1', 'subtype2)) + * Use ELGG_ENTITIES_NO_VALUE for no subtype. * * type_subtype_pairs => NULL|ARR (array('type' => 'subtype')) * (type = '$type' AND subtype = '$subtype') pairs * - * owner_guids => NULL|INT entity guid + * guids => NULL|ARR Array of entity guids + * + * owner_guids => NULL|ARR Array of owner guids * - * container_guids => NULL|INT container_guid + * container_guids => NULL|ARR Array of container_guids * - * site_guids => NULL (current_site)|INT site_guid + * site_guids => NULL (current_site)|ARR Array of site_guid * * order_by => NULL (time_created desc)|STR SQL order by clause * - * limit => NULL (10)|INT SQL limit clause + * reverse_order_by => BOOL Reverse the default order by clause + * + * limit => NULL (10)|INT SQL limit clause (0 means no limit) * * offset => NULL (0)|INT SQL offset clause * @@ -739,7 +893,9 @@ function get_entity($guid) { * * joins => array() Additional joins * - * @return mixed int if count is true, an array of entity objects, or false on failure + * callback => string A callback function to pass each row through + * + * @return mixed If count, int. If not count, array. false on errors. * @since 1.7.0 * @see elgg_get_entities_from_metadata() * @see elgg_get_entities_from_relationship() @@ -756,6 +912,7 @@ function elgg_get_entities(array $options = array()) { 'subtypes' => ELGG_ENTITIES_ANY_VALUE, 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE, + 'guids' => ELGG_ENTITIES_ANY_VALUE, 'owner_guids' => ELGG_ENTITIES_ANY_VALUE, 'container_guids' => ELGG_ENTITIES_ANY_VALUE, 'site_guids' => $CONFIG->site_guid, @@ -765,6 +922,7 @@ function elgg_get_entities(array $options = array()) { 'created_time_lower' => ELGG_ENTITIES_ANY_VALUE, 'created_time_upper' => ELGG_ENTITIES_ANY_VALUE, + 'reverse_order_by' => false, 'order_by' => 'e.time_created desc', 'group_by' => ELGG_ENTITIES_ANY_VALUE, 'limit' => 10, @@ -772,7 +930,11 @@ function elgg_get_entities(array $options = array()) { 'count' => FALSE, 'selects' => array(), 'wheres' => array(), - 'joins' => array() + 'joins' => array(), + + 'callback' => 'entity_row_to_elggstar', + + '__ElggBatch' => null, ); $options = array_merge($defaults, $options); @@ -788,7 +950,7 @@ function elgg_get_entities(array $options = array()) { } } - $singulars = array('type', 'subtype', 'owner_guid', 'container_guid', 'site_guid'); + $singulars = array('type', 'subtype', 'guid', 'owner_guid', 'container_guid', 'site_guid'); $options = elgg_normalise_plural_options_array($options, $singulars); // evaluate where clauses @@ -800,15 +962,15 @@ function elgg_get_entities(array $options = array()) { $wheres[] = elgg_get_entity_type_subtype_where_sql('e', $options['types'], $options['subtypes'], $options['type_subtype_pairs']); - $wheres[] = elgg_get_entity_site_where_sql('e', $options['site_guids']); - $wheres[] = elgg_get_entity_owner_where_sql('e', $options['owner_guids']); - $wheres[] = elgg_get_entity_container_where_sql('e', $options['container_guids']); + + $wheres[] = elgg_get_guid_based_where_sql('e.guid', $options['guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.owner_guid', $options['owner_guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.container_guid', $options['container_guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.site_guid', $options['site_guids']); + $wheres[] = elgg_get_entity_time_where_sql('e', $options['created_time_upper'], $options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']); - // remove identical where clauses - $wheres = array_unique($wheres); - // see if any functions failed // remove empty strings on successful functions foreach ($wheres as $i => $where) { @@ -819,6 +981,9 @@ function elgg_get_entities(array $options = array()) { } } + // remove identical where clauses + $wheres = array_unique($wheres); + // evaluate join clauses if (!is_array($options['joins'])) { $options['joins'] = array($options['joins']); @@ -839,7 +1004,7 @@ function elgg_get_entities(array $options = array()) { if ($options['selects']) { $selects = ''; foreach ($options['selects'] as $select) { - $selects = ", $select"; + $selects .= ", $select"; } } else { $selects = ''; @@ -865,24 +1030,53 @@ function elgg_get_entities(array $options = array()) { // Add access controls $query .= get_access_sql_suffix('e'); + + // reverse order by + if ($options['reverse_order_by']) { + $options['order_by'] = elgg_sql_reverse_order_by_clause($options['order_by']); + } + if (!$options['count']) { - if ($options['group_by'] = sanitise_string($options['group_by'])) { + if ($options['group_by']) { $query .= " GROUP BY {$options['group_by']}"; } - if ($options['order_by'] = sanitise_string($options['order_by'])) { + if ($options['order_by']) { $query .= " ORDER BY {$options['order_by']}"; } if ($options['limit']) { - $limit = sanitise_int($options['limit']); - $offset = sanitise_int($options['offset']); + $limit = sanitise_int($options['limit'], false); + $offset = sanitise_int($options['offset'], false); $query .= " LIMIT $offset, $limit"; } - $dt = get_data($query, "entity_row_to_elggstar"); + if ($options['callback'] === 'entity_row_to_elggstar') { + $dt = _elgg_fetch_entities_from_sql($query, $options['__ElggBatch']); + } else { + $dt = get_data($query, $options['callback']); + } + + if ($dt) { + // populate entity and metadata caches + $guids = array(); + foreach ($dt as $item) { + // A custom callback could result in items that aren't ElggEntity's, so check for them + if ($item instanceof ElggEntity) { + _elgg_cache_entity($item); + // plugins usually have only settings + if (!$item instanceof ElggPlugin) { + $guids[] = $item->guid; + } + } + } + // @todo Without this, recursive delete fails. See #4568 + reset($dt); - //@todo normalize this to array() + if ($guids) { + elgg_get_metadata_cache()->populateFromEntities($guids); + } + } return $dt; } else { $total = get_data_row($query); @@ -891,96 +1085,101 @@ function elgg_get_entities(array $options = array()) { } /** - * Returns entities. - * - * @deprecated 1.7. Use elgg_get_entities(). - * - * @param string $type Entity type - * @param string $subtype Entity subtype - * @param int $owner_guid Owner GUID - * @param string $order_by Order by clause - * @param int $limit Limit - * @param int $offset Offset - * @param bool $count Return a count or an array of entities - * @param int $site_guid Site GUID - * @param int $container_guid Container GUID - * @param int $timelower Lower time limit - * @param int $timeupper Upper time limit + * Return entities from an SQL query generated by elgg_get_entities. * - * @return array + * @param string $sql + * @param ElggBatch $batch + * @return ElggEntity[] + * + * @access private + * @throws LogicException */ -function get_entities($type = "", $subtype = "", $owner_guid = 0, $order_by = "", $limit = 10, -$offset = 0, $count = false, $site_guid = 0, $container_guid = null, $timelower = 0, -$timeupper = 0) { - - elgg_deprecated_notice('get_entities() was deprecated by elgg_get_entities().', 1.7); - - // rewrite owner_guid to container_guid to emulate old functionality - if ($owner_guid != "") { - if (is_null($container_guid)) { - $container_guid = $owner_guid; - $owner_guid = NULL; - } +function _elgg_fetch_entities_from_sql($sql, ElggBatch $batch = null) { + static $plugin_subtype; + if (null === $plugin_subtype) { + $plugin_subtype = get_subtype_id('object', 'plugin'); } - $options = array(); - if ($type) { - if (is_array($type)) { - $options['types'] = $type; - } else { - $options['type'] = $type; - } - } + // Keys are types, values are columns that, if present, suggest that the secondary + // table is already JOINed + $types_to_optimize = array( + 'object' => 'title', + 'user' => 'password', + 'group' => 'name', + ); - if ($subtype) { - if (is_array($subtype)) { - $options['subtypes'] = $subtype; - } else { - $options['subtype'] = $subtype; + $rows = get_data($sql); + + // guids to look up in each type + $lookup_types = array(); + // maps GUIDs to the $rows key + $guid_to_key = array(); + + if (isset($rows[0]->type, $rows[0]->subtype) + && $rows[0]->type === 'object' + && $rows[0]->subtype == $plugin_subtype) { + // Likely the entire resultset is plugins, which have already been optimized + // to JOIN the secondary table. In this case we allow retrieving from cache, + // but abandon the extra queries. + $types_to_optimize = array(); + } + + // First pass: use cache where possible, gather GUIDs that we're optimizing + foreach ($rows as $i => $row) { + if (empty($row->guid) || empty($row->type)) { + throw new LogicException('Entity row missing guid or type'); + } + if ($entity = _elgg_retrieve_cached_entity($row->guid)) { + $rows[$i] = $entity; + continue; + } + if (isset($types_to_optimize[$row->type])) { + // check if row already looks JOINed. + if (isset($row->{$types_to_optimize[$row->type]})) { + // Row probably already contains JOINed secondary table. Don't make another query just + // to pull data that's already there + continue; + } + $lookup_types[$row->type][] = $row->guid; + $guid_to_key[$row->guid] = $i; + } + } + // Do secondary queries and merge rows + if ($lookup_types) { + $dbprefix = elgg_get_config('dbprefix'); + + foreach ($lookup_types as $type => $guids) { + $set = "(" . implode(',', $guids) . ")"; + $sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set"; + $secondary_rows = get_data($sql); + if ($secondary_rows) { + foreach ($secondary_rows as $secondary_row) { + $key = $guid_to_key[$secondary_row->guid]; + // cast to arrays to merge then cast back + $rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row); + } + } } } - - if ($owner_guid) { - if (is_array($owner_guid)) { - $options['owner_guids'] = $owner_guid; + // Second pass to finish conversion + foreach ($rows as $i => $row) { + if ($row instanceof ElggEntity) { + continue; } else { - $options['owner_guid'] = $owner_guid; + try { + $rows[$i] = entity_row_to_elggstar($row); + } catch (IncompleteEntityException $e) { + // don't let incomplete entities throw fatal errors + unset($rows[$i]); + + // report incompletes to the batch process that spawned this query + if ($batch) { + $batch->reportIncompleteEntity($row); + } + } } } - - if ($order_by) { - $options['order_by'] = $order_by; - } - - // need to pass 0 for all option - $options['limit'] = $limit; - - if ($offset) { - $options['offset'] = $offset; - } - - if ($count) { - $options['count'] = $count; - } - - if ($site_guid) { - $options['site_guids'] = $site_guid; - } - - if ($container_guid) { - $options['container_guids'] = $container_guid; - } - - if ($timeupper) { - $options['created_time_upper'] = $timeupper; - } - - if ($timelower) { - $options['created_time_lower'] = $timelower; - } - - $r = elgg_get_entities($options); - return $r; + return $rows; } /** @@ -1007,8 +1206,8 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair return ''; } - // these are the only valid types for entities in elgg as defined in the DB. - $valid_types = array('object', 'user', 'group', 'site'); + // these are the only valid types for entities in elgg + $valid_types = elgg_get_config('entity_types'); // pairs override $wheres = array(); @@ -1034,7 +1233,7 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair foreach ($types as $type) { if (!in_array($type, $valid_types)) { $valid_types_count--; - unset ($types[array_search($type, $types)]); + unset($types[array_search($type, $types)]); } else { // do the checking (and decrementing) in the subtype section. $valid_subtypes_count += count($subtypes); @@ -1052,13 +1251,24 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair $subtype_ids = array(); if ($subtypes) { foreach ($subtypes as $subtype) { - // check that the subtype is valid (with ELGG_ENTITIES_NO_VALUE being a valid subtype) - if (ELGG_ENTITIES_NO_VALUE === $subtype || $subtype_id = get_subtype_id($type, $subtype)) { - $subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $subtype) ? ELGG_ENTITIES_NO_VALUE : $subtype_id; - } else { - $valid_subtypes_count--; - elgg_log("Type-subtype $type:$subtype' does not exist!", 'WARNING'); + // check that the subtype is valid + if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) { + // subtype value is 0 + $subtype_ids[] = ELGG_ENTITIES_NO_VALUE; + } elseif (!$subtype) { + // subtype is ignored. + // this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0 continue; + } else { + $subtype_id = get_subtype_id($type, $subtype); + + if ($subtype_id) { + $subtype_ids[] = $subtype_id; + } else { + $valid_subtypes_count--; + elgg_log("Type-subtype '$type:$subtype' does not exist!", 'NOTICE'); + continue; + } } } @@ -1086,7 +1296,7 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair foreach ($pairs as $paired_type => $paired_subtypes) { if (!in_array($paired_type, $valid_types)) { $valid_pairs_count--; - unset ($pairs[array_search($paired_type, $pairs)]); + unset($pairs[array_search($paired_type, $pairs)]); } else { if ($paired_subtypes && !is_array($paired_subtypes)) { $pairs[$paired_type] = array($paired_subtypes); @@ -1111,7 +1321,7 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair ELGG_ENTITIES_NO_VALUE : $paired_subtype_id; } else { $valid_pairs_subtypes_count--; - elgg_log("Type-subtype $paired_type:$paired_subtype' does not exist!", 'WARNING'); + elgg_log("Type-subtype '$paired_type:$paired_subtype' does not exist!", 'NOTICE'); // return false if we're all invalid subtypes in the only valid type continue; } @@ -1145,83 +1355,44 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair /** * Returns SQL where clause for owner and containers. * - * @todo Probably DRY up once things are settled. - * - * @param string $table Entity table prefix as defined in SELECT...FROM entities $table - * @param NULL|array $owner_guids Owner GUIDs + * @param string $column Column name the guids should be checked against. Usually + * best to provide in table.column format. + * @param NULL|array $guids Array of GUIDs. * - * @return FALSE|str - * @since 1.7.0 + * @return false|string + * @since 1.8.0 * @access private */ -function elgg_get_entity_owner_where_sql($table, $owner_guids) { +function elgg_get_guid_based_where_sql($column, $guids) { // short circuit if nothing requested - // 0 is a valid owner_guid. - if (!$owner_guids && $owner_guids !== 0) { + // 0 is a valid guid + if (!$guids && $guids !== 0) { return ''; } // normalize and sanitise owners - if (!is_array($owner_guids)) { - $owner_guids = array($owner_guids); - } - - $owner_guids_sanitised = array(); - foreach ($owner_guids as $owner_guid) { - if (($owner_guid != sanitise_int($owner_guid))) { - return FALSE; - } - $owner_guids_sanitised[] = $owner_guid; - } - - $where = ''; - - // implode(',', 0) returns 0. - if (($owner_str = implode(',', $owner_guids_sanitised)) - && ($owner_str !== FALSE) && ($owner_str !== '')) { - - $where = "({$table}.owner_guid IN ($owner_str))"; + if (!is_array($guids)) { + $guids = array($guids); } - return $where; -} + $guids_sanitized = array(); + foreach ($guids as $guid) { + if ($guid !== ELGG_ENTITIES_NO_VALUE) { + $guid = sanitise_int($guid); -/** - * Returns SQL where clause for containers. - * - * @param string $table Entity table prefix as defined in - * SELECT...FROM entities $table - * @param NULL|array $container_guids Array of container guids - * - * @return FALSE|string - * @since 1.7.0 - * @access private - */ -function elgg_get_entity_container_where_sql($table, $container_guids) { - // short circuit if nothing is requested. - // 0 is a valid container_guid. - if (!$container_guids && $container_guids !== 0) { - return ''; - } - - // normalize and sanitise containers - if (!is_array($container_guids)) { - $container_guids = array($container_guids); - } - - $container_guids_sanitised = array(); - foreach ($container_guids as $container_guid) { - if (($container_guid != sanitise_int($container_guid))) { - return FALSE; + if (!$guid) { + return false; + } } - $container_guids_sanitised[] = $container_guid; + $guids_sanitized[] = $guid; } $where = ''; + $guid_str = implode(',', $guids_sanitized); // implode(',', 0) returns 0. - if (FALSE !== $container_str = implode(',', $container_guids_sanitised)) { - $where = "({$table}.container_guid IN ($container_str))"; + if ($guid_str !== FALSE && $guid_str !== '') { + $where = "($column IN ($guid_str))"; } return $where; @@ -1232,12 +1403,12 @@ function elgg_get_entity_container_where_sql($table, $container_guids) { * * @param string $table Entity table prefix as defined in * SELECT...FROM entities $table - * @param NULL|int $time_created_upper Time crated upper limit + * @param NULL|int $time_created_upper Time created upper limit * @param NULL|int $time_created_lower Time created lower limit * @param NULL|int $time_updated_upper Time updated upper limit * @param NULL|int $time_updated_lower Time updated lower limit * - * @return FALSE|str FALSE on fail, string on success. + * @return FALSE|string FALSE on fail, string on success. * @since 1.7.0 * @access private */ @@ -1272,41 +1443,6 @@ $time_created_lower = NULL, $time_updated_upper = NULL, $time_updated_lower = NU } /** - * Returns SQL where clause for site entities - * - * @param string $table Entity table prefix as defined in SELECT...FROM entities $table - * @param NULL|array $site_guids Array of site guids - * - * @return FALSE|string - * @since 1.7.0 - * @access private - */ -function elgg_get_entity_site_where_sql($table, $site_guids) { - // short circuit if nothing requested - if (!$site_guids) { - return ''; - } - - if (!is_array($site_guids)) { - $site_guids = array($site_guids); - } - - $site_guids_sanitised = array(); - foreach ($site_guids as $site_guid) { - if (!$site_guid || ($site_guid != sanitise_int($site_guid))) { - return FALSE; - } - $site_guids_sanitised[] = $site_guid; - } - - if ($site_guids_str = implode(',', $site_guids_sanitised)) { - return "({$table}.site_guid IN ($site_guids_str))"; - } - - return ''; -} - -/** * Returns a string of parsed entities. * * Displays list of entities with formatting specified @@ -1314,12 +1450,16 @@ function elgg_get_entity_site_where_sql($table, $site_guids) { * * @tip Pagination is handled automatically. * + * @internal This also provides the views for elgg_view_annotation(). + * * @param array $options Any options from $getter options plus: - * full_view => BOOL Display full view entities - * view_type_toggle => BOOL Display gallery / list switch - * pagination => BOOL Display pagination links - * + * full_view => BOOL Display full view entities + * list_type => STR 'list' or 'gallery' + * list_type_toggle => BOOL Display gallery / list switch + * pagination => BOOL Display pagination links + * * @param mixed $getter The entity getter function to use to fetch the entities + * @param mixed $viewer The function to use to view the entity list. * * @return string * @since 1.7 @@ -1327,107 +1467,38 @@ function elgg_get_entity_site_where_sql($table, $site_guids) { * @see elgg_view_entity_list() * @link http://docs.elgg.org/Entities/Output */ -function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entities') { +function elgg_list_entities(array $options = array(), $getter = 'elgg_get_entities', + $viewer = 'elgg_view_entity_list') { + + global $autofeed; + $autofeed = true; + + $offset_key = isset($options['offset_key']) ? $options['offset_key'] : 'offset'; + $defaults = array( - 'offset' => (int) max(get_input('offset', 0), 0), + 'offset' => (int) max(get_input($offset_key, 0), 0), 'limit' => (int) max(get_input('limit', 10), 0), 'full_view' => TRUE, - 'view_type_toggle' => FALSE, + 'list_type_toggle' => FALSE, 'pagination' => TRUE, ); - + $options = array_merge($defaults, $options); + //backwards compatibility + if (isset($options['view_type_toggle'])) { + $options['list_type_toggle'] = $options['view_type_toggle']; + } + $options['count'] = TRUE; $count = $getter($options); - + $options['count'] = FALSE; $entities = $getter($options); - return elgg_view_entity_list($entities, $count, $options['offset'], $options['limit'], - $options['full_view'], $options['view_type_toggle'], $options['pagination']); -} - -/** - * Lists entities - * - * @deprecated 1.7. Use elgg_list_entities(). - * - * @param string $type Entity type - * @param string $subtype Entity subtype - * @param int $owner_guid Owner GUID - * @param int $limit Limit - * @param bool $fullview Display entity full views? - * @param bool $viewtypetoggle Allow switching to gallery mode? - * @param bool $pagination Show pagination? - * - * @return string - */ -function list_entities($type= "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, -$viewtypetoggle = false, $pagination = true) { - - elgg_deprecated_notice('list_entities() was deprecated by elgg_list_entities()!', 1.7); - - $options = array(); - - // rewrite owner_guid to container_guid to emulate old functionality - if ($owner_guid) { - $options['container_guids'] = $owner_guid; - } - - if ($type) { - $options['types'] = $type; - } - - if ($subtype) { - $options['subtypes'] = $subtype; - } + $options['count'] = $count; - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset = sanitise_int(get_input('offset', null))) { - $options['offset'] = $offset; - } - - $options['full_view'] = $fullview; - $options['view_type_toggle'] = $viewtypetoggle; - $options['pagination'] = $pagination; - - return elgg_list_entities($options); -} - -/** - * Lists entities that belong to a group. - * - * @warning This function is redundant and will be deprecated in 1.8. - * The preferred method of listing group entities is by setting the - * container guid option in {@link elgg_list_entities()}. - * - * @param string $subtype The arbitrary subtype of the entity - * @param int $owner_guid The GUID of the owning user - * @param int $container_guid The GUID of the containing group - * @param int $limit The number of entities to display per page (default: 10) - * @param bool $fullview Whether or not to display the full view (default: true) - * @param bool $viewtypetoggle Whether or not to allow gallery view (default: true) - * @param bool $pagination Whether to display pagination (default: true) - * - * @return string List of parsed entities - * - * @see elgg_list_entities() - */ -function list_entities_groups($subtype = "", $owner_guid = 0, $container_guid = 0, -$limit = 10, $fullview = true, $viewtypetoggle = true, $pagination = true) { - - $offset = (int) get_input('offset'); - $count = get_objects_in_group($container_guid, $subtype, $owner_guid, - 0, "", $limit, $offset, true); - $entities = get_objects_in_group($container_guid, $subtype, $owner_guid, - 0, "", $limit, $offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, - $fullview, $viewtypetoggle, $pagination); + return $viewer($entities, $options); } /** @@ -1435,13 +1506,15 @@ $limit = 10, $fullview = true, $viewtypetoggle = true, $pagination = true) { * * @tip Use this to generate a list of archives by month for when entities were added or updated. * + * @todo document how to pass in array for $subtype + * * @warning Months are returned in the form YYYYMM. * * @param string $type The type of entity * @param string $subtype The subtype of entity - * @param int $container_guid The container GUID that the entinties belong to + * @param int $container_guid The container GUID that the entities belong to * @param int $site_guid The site GUID - * @param str $order_by Order_by SQL order by clause + * @param string $order_by Order_by SQL order by clause * * @return array|false Either an array months as YYYYMM, or false on failure */ @@ -1548,9 +1621,9 @@ $order_by = 'time_created') { * @param bool $recursive Recursively disable all entities owned or contained by $guid? * * @return bool - * @access private * @see access_show_hidden_entities() * @link http://docs.elgg.org/Entities + * @access private */ function disable_entity($guid, $reason = "", $recursive = true) { global $CONFIG; @@ -1559,36 +1632,41 @@ function disable_entity($guid, $reason = "", $recursive = true) { $reason = sanitise_string($reason); if ($entity = get_entity($guid)) { - if (trigger_elgg_event('disable', $entity->type, $entity)) { + if (elgg_trigger_event('disable', $entity->type, $entity)) { if ($entity->canEdit()) { if ($reason) { create_metadata($guid, 'disable_reason', $reason, '', 0, ACCESS_PUBLIC); } if ($recursive) { - // Temporary token overriding access controls - // @todo Do this better. - static $__RECURSIVE_DELETE_TOKEN; - // Make it slightly harder to guess - $__RECURSIVE_DELETE_TOKEN = md5(get_loggedin_userid()); - - $sub_entities = get_data("SELECT * from {$CONFIG->dbprefix}entities - WHERE container_guid=$guid - or owner_guid=$guid - or site_guid=$guid", 'entity_row_to_elggstar'); + $hidden = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $ia = elgg_set_ignore_access(true); + + $sub_entities = get_data("SELECT * FROM {$CONFIG->dbprefix}entities + WHERE ( + container_guid = $guid + OR owner_guid = $guid + OR site_guid = $guid + ) AND enabled='yes'", 'entity_row_to_elggstar'); if ($sub_entities) { foreach ($sub_entities as $e) { + add_entity_relationship($e->guid, 'disabled_with', $entity->guid); $e->disable($reason); } } - - $__RECURSIVE_DELETE_TOKEN = null; + access_show_hidden_entities($hidden); + elgg_set_ignore_access($ia); } + $entity->disableMetadata(); + $entity->disableAnnotations(); + _elgg_invalidate_cache_for_entity($guid); + $res = update_data("UPDATE {$CONFIG->dbprefix}entities - set enabled='no' - where guid={$guid}"); + SET enabled = 'no' + WHERE guid = $guid"); return $res; } @@ -1600,40 +1678,55 @@ function disable_entity($guid, $reason = "", $recursive = true) { /** * Enable an entity. * - * @warning In order to enable an entity using ElggEntity::enable(), - * you must first use {@link access_show_hidden_entities()}. + * @warning In order to enable an entity, you must first use + * {@link access_show_hidden_entities()}. * - * @param int $guid GUID of entity to enable + * @param int $guid GUID of entity to enable + * @param bool $recursive Recursively enable all entities disabled with the entity? * * @return bool */ -function enable_entity($guid) { +function enable_entity($guid, $recursive = true) { global $CONFIG; $guid = (int)$guid; // Override access only visible entities - $access_status = access_get_show_hidden_status(); + $old_access_status = access_get_show_hidden_status(); access_show_hidden_entities(true); + $result = false; if ($entity = get_entity($guid)) { - if (trigger_elgg_event('enable', $entity->type, $entity)) { + if (elgg_trigger_event('enable', $entity->type, $entity)) { if ($entity->canEdit()) { - access_show_hidden_entities($access_status); - $result = update_data("UPDATE {$CONFIG->dbprefix}entities - set enabled='yes' - where guid={$guid}"); - $entity->clearMetaData('disable_reason'); + SET enabled = 'yes' + WHERE guid = $guid"); - return $result; + $entity->deleteMetadata('disable_reason'); + $entity->enableMetadata(); + $entity->enableAnnotations(); + + if ($recursive) { + $disabled_with_it = elgg_get_entities_from_relationship(array( + 'relationship' => 'disabled_with', + 'relationship_guid' => $entity->guid, + 'inverse_relationship' => true, + 'limit' => 0, + )); + + foreach ($disabled_with_it as $e) { + $e->enable(); + remove_entity_relationship($e->guid, 'disabled_with', $entity->guid); + } + } } } } - access_show_hidden_entities($access_status); - return false; + access_show_hidden_entities($old_access_status); + return $result; } /** @@ -1654,20 +1747,29 @@ function enable_entity($guid) { * @param bool $recursive If true (default) then all entities which are * owned or contained by $guid will also be deleted. * - * @access private * @return bool + * @access private */ function delete_entity($guid, $recursive = true) { global $CONFIG, $ENTITY_CACHE; $guid = (int)$guid; if ($entity = get_entity($guid)) { - if (trigger_elgg_event('delete', $entity->type, $entity)) { + if (elgg_trigger_event('delete', $entity->type, $entity)) { if ($entity->canEdit()) { // delete cache if (isset($ENTITY_CACHE[$guid])) { - invalidate_cache_for_entity($guid); + _elgg_invalidate_cache_for_entity($guid); + } + + // If memcache is available then delete this entry from the cache + static $newentity_cache; + if ((!$newentity_cache) && (is_memcache_available())) { + $newentity_cache = new ElggMemcache('new_entity_cache'); + } + if ($newentity_cache) { + $newentity_cache->delete($guid); } // Delete contained owned and otherwise releated objects (depth first) @@ -1676,28 +1778,53 @@ function delete_entity($guid, $recursive = true) { // @todo Do this better. static $__RECURSIVE_DELETE_TOKEN; // Make it slightly harder to guess - $__RECURSIVE_DELETE_TOKEN = md5(get_loggedin_userid()); - - $sub_entities = get_data("SELECT * from {$CONFIG->dbprefix}entities - WHERE container_guid=$guid - or owner_guid=$guid - or site_guid=$guid", 'entity_row_to_elggstar'); - if ($sub_entities) { - foreach ($sub_entities as $e) { - $e->delete(); - } + $__RECURSIVE_DELETE_TOKEN = md5(elgg_get_logged_in_user_guid()); + + $entity_disable_override = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $ia = elgg_set_ignore_access(true); + + // @todo there was logic in the original code that ignored + // entities with owner or container guids of themselves. + // this should probably be prevented in ElggEntity instead of checked for here + $options = array( + 'wheres' => array( + "((container_guid = $guid OR owner_guid = $guid OR site_guid = $guid)" + . " AND guid != $guid)" + ), + 'limit' => 0 + ); + + $batch = new ElggBatch('elgg_get_entities', $options); + $batch->setIncrementOffset(false); + + foreach ($batch as $e) { + $e->delete(true); } + access_show_hidden_entities($entity_disable_override); $__RECURSIVE_DELETE_TOKEN = null; + elgg_set_ignore_access($ia); } + $entity_disable_override = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $ia = elgg_set_ignore_access(true); + // Now delete the entity itself - $entity->clearMetadata(); - $entity->clearAnnotations(); - $entity->clearRelationships(); - remove_from_river_by_subject($guid); - remove_from_river_by_object($guid); + $entity->deleteMetadata(); + $entity->deleteOwnedMetadata(); + $entity->deleteAnnotations(); + $entity->deleteOwnedAnnotations(); + $entity->deleteRelationships(); + + access_show_hidden_entities($entity_disable_override); + elgg_set_ignore_access($ia); + + elgg_delete_river(array('subject_guid' => $guid)); + elgg_delete_river(array('object_guid' => $guid)); remove_all_private_settings($guid); + $res = delete_data("DELETE from {$CONFIG->dbprefix}entities where guid={$guid}"); if ($res) { $sub_table = ""; @@ -1723,7 +1850,7 @@ function delete_entity($guid, $recursive = true) { } } - return $res; + return (bool)$res; } } } @@ -1732,25 +1859,6 @@ function delete_entity($guid, $recursive = true) { } /** - * Delete multiple entities that match a given query. - * This function iterates through and calls delete_entity on - * each one, this is somewhat inefficient but lets - * the 'delete' event be called for each entity. - * - * @deprecated 1.7. This is a dangerous function as it defaults to deleting everything. - * - * @param string $type The type of entity (eg "user", "object" etc) - * @param string $subtype The arbitrary subtype of the entity - * @param int $owner_guid The GUID of the owning user - * - * @return false - */ -function delete_entities($type = "", $subtype = "", $owner_guid = 0) { - elgg_deprecated_notice('delete_entities() was deprecated because no one should use it.', 1.7); - return false; -} - -/** * Exports attributes generated on the fly (volatile) about an entity. * * @param string $hook volatile @@ -1758,7 +1866,7 @@ function delete_entities($type = "", $subtype = "", $owner_guid = 0) { * @param string $returnvalue Return value from previous hook * @param array $params The parameters, passed 'guid' and 'varname' * - * @return null + * @return ElggMetadata|null * @elgg_plugin_hook_handler volatile metadata * @todo investigate more. * @access private @@ -1802,6 +1910,9 @@ function volatile_data_export_plugin_hook($hook, $entity_type, $returnvalue, $pa * * @elgg_event_handler export all * @return mixed + * @access private + * + * @throws InvalidParameterException|InvalidClassException */ function export_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { // Sanity check values @@ -1818,7 +1929,7 @@ function export_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { // Get the entity $entity = get_entity($guid); if (!($entity instanceof ElggEntity)) { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class()); + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class())); throw new InvalidClassException($msg); } @@ -1843,6 +1954,9 @@ function export_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { * * @return ElggEntity the unsaved entity which should be populated by items. * @todo Remove this. + * @access private + * + * @throws ClassException|InstallationException|ImportException */ function oddentity_to_elggentity(ODDEntity $element) { $class = $element->getAttribute('class'); @@ -1854,16 +1968,16 @@ function oddentity_to_elggentity(ODDEntity $element) { if (!$tmp) { // Construct new class with owner from session $classname = get_subtype_class($class, $subclass); - if ($classname != "") { + if ($classname) { if (class_exists($classname)) { $tmp = new $classname(); if (!($tmp instanceof ElggEntity)) { - $msg = sprintf(elgg_echo('ClassException:ClassnameNotClass', $classname, get_class())); + $msg = elgg_echo('ClassException:ClassnameNotClass', array($classname, get_class())); throw new ClassException($msg); } } else { - error_log(sprintf(elgg_echo('ClassNotFoundException:MissingClass'), $classname)); + error_log(elgg_echo('ClassNotFoundException:MissingClass', array($classname))); } } else { switch ($class) { @@ -1880,7 +1994,7 @@ function oddentity_to_elggentity(ODDEntity $element) { $tmp = new ElggSite($row); break; default: - $msg = sprintf(elgg_echo('InstallationException:TypeNotSupported'), $class); + $msg = elgg_echo('InstallationException:TypeNotSupported', array($class)); throw new InstallationException($msg); } } @@ -1888,7 +2002,7 @@ function oddentity_to_elggentity(ODDEntity $element) { if ($tmp) { if (!$tmp->import($element)) { - $msg = sprintf(elgg_echo('ImportException:ImportFailed'), $element->getAttribute('uuid')); + $msg = elgg_echo('ImportException:ImportFailed', array($element->getAttribute('uuid'))); throw new ImportException($msg); } @@ -1913,12 +2027,14 @@ function oddentity_to_elggentity(ODDEntity $element) { * @return mixed * @elgg_plugin_hook_handler import all * @todo document + * @access private * + * @throws ImportException */ function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { $element = $params['element']; - $tmp = NULL; + $tmp = null; if ($element instanceof ODDEntity) { $tmp = oddentity_to_elggentity($element); @@ -1926,7 +2042,7 @@ function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { if ($tmp) { // Make sure its saved if (!$tmp->save()) { - sprintf(elgg_echo('ImportException:ProblemSaving'), $element->getAttribute('uuid')); + $msg = elgg_echo('ImportException:ProblemSaving', array($element->getAttribute('uuid'))); throw new ImportException($msg); } @@ -1960,20 +2076,18 @@ function import_entity_plugin_hook($hook, $entity_type, $returnvalue, $params) { * @link http://docs.elgg.org/Entities/AccessControl */ function can_edit_entity($entity_guid, $user_guid = 0) { - global $CONFIG; - $user_guid = (int)$user_guid; $user = get_entity($user_guid); if (!$user) { - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); } + $return = false; if ($entity = get_entity($entity_guid)) { - $return = false; // Test user if possible - should default to false unless a plugin hook says otherwise if ($user) { - if ($entity->getOwner() == $user->getGUID()) { + if ($entity->getOwnerGUID() == $user->getGUID()) { $return = true; } if ($entity->container_guid == $user->getGUID()) { @@ -1988,13 +2102,10 @@ function can_edit_entity($entity_guid, $user_guid = 0) { } } } + } - return trigger_plugin_hook('permissions_check', $entity->type, + return elgg_trigger_plugin_hook('permissions_check', $entity->type, array('entity' => $entity, 'user' => $user), $return); - - } else { - return false; - } } /** @@ -2010,23 +2121,28 @@ function can_edit_entity($entity_guid, $user_guid = 0) { * @param ElggMetadata $metadata The metadata to specifically check (if any; default null) * * @return bool - * @see register_plugin_hook() + * @see elgg_register_plugin_hook_handler() */ function can_edit_entity_metadata($entity_guid, $user_guid = 0, $metadata = null) { if ($entity = get_entity($entity_guid)) { $return = null; - if ($metadata->owner_guid == 0) { + if ($metadata && ($metadata->owner_guid == 0)) { $return = true; } if (is_null($return)) { $return = can_edit_entity($entity_guid, $user_guid); } - $user = get_entity($user_guid); + if ($user_guid) { + $user = get_entity($user_guid); + } else { + $user = elgg_get_logged_in_user_entity(); + } + $params = array('entity' => $entity, 'user' => $user, 'metadata' => $metadata); - $return = trigger_plugin_hook('permissions_check:metadata', $entity->type, $parms, $return); + $return = elgg_trigger_plugin_hook('permissions_check:metadata', $entity->type, $params, $return); return $return; } else { return false; @@ -2034,79 +2150,6 @@ function can_edit_entity_metadata($entity_guid, $user_guid = 0, $metadata = null } /** - * Return the icon URL for an entity. - * - * @tip Can be overridden by registering a plugin hook for entity:icon:url, $entity_type. - * - * @internal This is passed an entity rather than a guid to handle non-created entities. - * - * @param ElggEntity $entity The entity - * @param string $size Icon size - * - * @return string URL to the entity icon. - */ -function get_entity_icon_url(ElggEntity $entity, $size = 'medium') { - global $CONFIG; - - $size = sanitise_string($size); - switch (strtolower($size)) { - case 'master': - $size = 'master'; - break; - - case 'large' : - $size = 'large'; - break; - - case 'topbar' : - $size = 'topbar'; - break; - - case 'tiny' : - $size = 'tiny'; - break; - - case 'small' : - $size = 'small'; - break; - - case 'medium' : - default: - $size = 'medium'; - } - - $url = false; - - $viewtype = elgg_get_viewtype(); - - // Step one, see if anyone knows how to render this in the current view - $params = array('entity' => $entity, 'viewtype' => $viewtype, 'size' => $size); - $url = trigger_plugin_hook('entity:icon:url', $entity->getType(), $params, $url); - - // Fail, so use default - if (!$url) { - $type = $entity->getType(); - $subtype = $entity->getSubtype(); - - if (!empty($subtype)) { - $overrideurl = elgg_view("icon/{$type}/{$subtype}/{$size}", array('entity' => $entity)); - if (!empty($overrideurl)) { - return $overrideurl; - } - } - - $overrideurl = elgg_view("icon/{$type}/default/{$size}", array('entity' => $entity)); - if (!empty($overrideurl)) { - return $overrideurl; - } - - $url = "_graphics/icons/default/$size.png"; - } - - return elgg_normalize_url($url); -} - -/** * Returns the URL for an entity. * * @tip Can be overridden with {@link register_entity_url_handler()}. @@ -2125,26 +2168,25 @@ function get_entity_url($entity_guid) { if (isset($CONFIG->entity_url_handler[$entity->getType()][$entity->getSubType()])) { $function = $CONFIG->entity_url_handler[$entity->getType()][$entity->getSubType()]; if (is_callable($function)) { - $url = $function($entity); + $url = call_user_func($function, $entity); } } elseif (isset($CONFIG->entity_url_handler[$entity->getType()]['all'])) { $function = $CONFIG->entity_url_handler[$entity->getType()]['all']; if (is_callable($function)) { - $url = $function($entity); + $url = call_user_func($function, $entity); } } elseif (isset($CONFIG->entity_url_handler['all']['all'])) { $function = $CONFIG->entity_url_handler['all']['all']; if (is_callable($function)) { - $url = $function($entity); + $url = call_user_func($function, $entity); } } if ($url == "") { - $url = "pg/view/" . $entity_guid; + $url = "view/" . $entity_guid; } - - return elgg_normalize_url($url); + return elgg_normalize_url($url); } return false; @@ -2153,20 +2195,19 @@ function get_entity_url($entity_guid) { /** * Sets the URL handler for a particular entity type and subtype * - * @param string $function_name The function to register * @param string $entity_type The entity type * @param string $entity_subtype The entity subtype + * @param string $function_name The function to register * - * @return true|false Depending on success + * @return bool Depending on success * @see get_entity_url() * @see ElggEntity::getURL() + * @since 1.8.0 */ -function register_entity_url_handler($function_name, $entity_type = "all", -$entity_subtype = "all") { - +function elgg_register_entity_url_handler($entity_type, $entity_subtype, $function_name) { global $CONFIG; - if (!is_callable($function_name)) { + if (!is_callable($function_name, true)) { return false; } @@ -2184,46 +2225,6 @@ $entity_subtype = "all") { } /** - * Default Icon handler for entities. - * - * @tip This will attempt to find a default entity for the current view and return a url. - * This is registered at a high priority so that other handlers will pick it up first. - * - * @param string $hook entity:icon:url - * @param string $entity_type all - * @param mixed $returnvalue Previous hook's return value - * @param array $params Array of params - * - * @return string|null String of URL for entity's icon - * @elgg_plugin_hook_handler entity:icon:url all - */ -function default_entity_icon_hook($hook, $entity_type, $returnvalue, $params) { - global $CONFIG; - - if ((!$returnvalue) && ($hook == 'entity:icon:url')) { - $entity = $params['entity']; - $type = $entity->type; - $subtype = get_subtype_from_id($entity->subtype); - $viewtype = $params['viewtype']; - $size = $params['size']; - - $url = "views/$viewtype/graphics/icons/$type/$subtype/$size.png"; - - if (!@file_exists($CONFIG->path . $url)) { - $url = "views/$viewtype/graphics/icons/$type/default/$size.png"; - } - - if (!@file_exists($CONFIG->path . $url)) { - $url = "views/$viewtype/graphics/icons/default/$size.png"; - } - - if (@file_exists($CONFIG->path . $url)) { - return elgg_get_site_url().$url; - } - } -} - -/** * Registers an entity type and subtype as a public-facing entity that should * be shown in search and by {@link elgg_list_registered_entities()}. * @@ -2234,16 +2235,16 @@ function default_entity_icon_hook($hook, $entity_type, $returnvalue, $params) { * @param string $type The type of entity (object, site, user, group) * @param string $subtype The subtype to register (may be blank) * - * @return true|false Depending on success + * @return bool Depending on success * @see get_registered_entity_types() * @link http://docs.elgg.org/Search * @link http://docs.elgg.org/Tutorials/Search */ -function register_entity_type($type, $subtype) { +function elgg_register_entity_type($type, $subtype = null) { global $CONFIG; $type = strtolower($type); - if (!in_array($type, array('object', 'site', 'group', 'user'))) { + if (!in_array($type, $CONFIG->entity_types)) { return FALSE; } @@ -2271,14 +2272,14 @@ function register_entity_type($type, $subtype) { * @param string $type The type of entity (object, site, user, group) * @param string $subtype The subtype to register (may be blank) * - * @return true|false Depending on success - * @see register_entity_type() + * @return bool Depending on success + * @see elgg_register_entity_type() */ function unregister_entity_type($type, $subtype) { global $CONFIG; $type = strtolower($type); - if (!in_array($type, array('object', 'site', 'group', 'user'))) { + if (!in_array($type, $CONFIG->entity_types)) { return FALSE; } @@ -2310,15 +2311,15 @@ function unregister_entity_type($type, $subtype) { * @param string $type The type of entity (object, site, user, group) or blank for all * * @return array|false Depending on whether entities have been registered - * @see register_entity_type() + * @see elgg_register_entity_type() */ -function get_registered_entity_types($type = '') { +function get_registered_entity_types($type = null) { global $CONFIG; if (!isset($CONFIG->registered_entities)) { return false; } - if (!empty($type)) { + if ($type) { $type = strtolower($type); } if (!empty($type) && empty($CONFIG->registered_entities[$type])) { @@ -2333,26 +2334,32 @@ function get_registered_entity_types($type = '') { } /** - * Returns if the entity type and subtype have been registered with {@see register_entity_type()}. + * Returns if the entity type and subtype have been registered with {@see elgg_register_entity_type()}. * * @param string $type The type of entity (object, site, user, group) * @param string $subtype The subtype (may be blank) * - * @return true|false Depending on whether or not the type has been registered + * @return bool Depending on whether or not the type has been registered */ -function is_registered_entity_type($type, $subtype) { +function is_registered_entity_type($type, $subtype = null) { global $CONFIG; if (!isset($CONFIG->registered_entities)) { return false; } + $type = strtolower($type); - if (empty($CONFIG->registered_entities[$type])) { + + // @todo registering a subtype implicitly registers the type. + // see #2684 + if (!isset($CONFIG->registered_entities[$type])) { return false; } - if (in_array($subtype, $CONFIG->registered_entities[$type])) { - return true; + + if ($subtype && !in_array($subtype, $CONFIG->registered_entities[$type])) { + return false; } + return true; } /** @@ -2360,56 +2367,18 @@ function is_registered_entity_type($type, $subtype) { * * @param array $page Page elements from pain page handler * - * @return void + * @return bool * @elgg_page_handler view + * @access private */ function entities_page_handler($page) { if (isset($page[0])) { global $CONFIG; set_input('guid', $page[0]); include($CONFIG->path . "pages/entities/index.php"); + return true; } -} - -/** - * Lists entities. - * - * @param int $owner_guid Owner GUID - * @param int $limit Limit - * @param bool $fullview Show entity full views - * @param bool $viewtypetoggle Show list type toggle - * @param bool $allowedtypes A string of the allowed types - * - * @return string - * @deprecated 1.7. Use elgg_list_registered_entities(). - */ -function list_registered_entities($owner_guid = 0, $limit = 10, $fullview = true, -$viewtypetoggle = false, $allowedtypes = true) { - - elgg_deprecated_notice('list_registered_entities() was deprecated by elgg_list_registered_entities().', 1.7); - - $options = array(); - - // don't want to send anything if not being used. - if ($owner_guid) { - $options['owner_guid'] = $owner_guid; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($allowedtypes) { - $options['allowed_types'] = $allowedtypes; - } - - // need to send because might be BOOL - $options['full_view'] = $fullview; - $options['view_type_toggle'] = $viewtypetoggle; - - $options['offset'] = get_input('offset', 0); - - return elgg_list_registered_entities($options); + return false; } /** @@ -2421,7 +2390,7 @@ $viewtypetoggle = false, $allowedtypes = true) { * * full_view => BOOL Display full view entities * - * view_type_toggle => BOOL Display gallery / list switch + * list_type_toggle => BOOL Display gallery / list switch * * allowed_types => TRUE|ARRAY True to show all types or an array of valid types. * @@ -2430,496 +2399,56 @@ $viewtypetoggle = false, $allowedtypes = true) { * @return string A viewable list of entities * @since 1.7.0 */ -function elgg_list_registered_entities($options) { +function elgg_list_registered_entities(array $options = array()) { + global $autofeed; + $autofeed = true; + $defaults = array( 'full_view' => TRUE, 'allowed_types' => TRUE, - 'view_type_toggle' => FALSE, + 'list_type_toggle' => FALSE, 'pagination' => TRUE, - 'offset' => 0 + 'offset' => 0, + 'types' => array(), + 'type_subtype_pairs' => array() ); $options = array_merge($defaults, $options); - $typearray = array(); - - if ($object_types = get_registered_entity_types()) { - foreach ($object_types as $object_type => $subtype_array) { - if (in_array($object_type, $options['allowed_types']) || $options['allowed_types'] === TRUE) { - $typearray[$object_type] = array(); - - if (is_array($subtype_array) && count($subtype_array)) { - foreach ($subtype_array as $subtype) { - $typearray[$object_type][] = $subtype; - } - } - } - } - } - - $options['type_subtype_pairs'] = $typearray; - - $count = elgg_get_entities(array_merge(array('count' => TRUE), $options)); - $entities = elgg_get_entities($options); - - return elgg_view_entity_list($entities, $count, $options['offset'], - $options['limit'], $options['full_view'], $options['view_type_toggle'], $options['pagination']); -} - -/** - * Get entities based on their private data. - * - * @param string $name The name of the setting - * @param string $value The value of the setting - * @param string $type The type of entity (eg "user", "object" etc) - * @param string $subtype The arbitrary subtype of the entity - * @param int $owner_guid The GUID of the owning user - * @param string $order_by The field to order by; by default, time_created desc - * @param int $limit The number of entities to return; 10 by default - * @param int $offset The indexing offset, 0 by default - * @param boolean $count Return a count of entities - * @param int $site_guid The site to get entities for. 0 for current, -1 for any - * @param mixed $container_guid The container(s) GUIDs - * - * @return array A list of entities. - * @todo deprecate - */ -function get_entities_from_private_setting($name = "", $value = "", $type = "", $subtype = "", -$owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, -$container_guid = null) { - global $CONFIG; - - if ($subtype === false || $subtype === null || $subtype === 0) { - return false; - } - - $name = sanitise_string($name); - $value = sanitise_string($value); - - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - $order_by = sanitise_string($order_by); - $limit = (int)$limit; - $offset = (int)$offset; - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; + //backwards compatibility + if (isset($options['view_type_toggle'])) { + $options['list_type_toggle'] = $options['view_type_toggle']; } - $where = array(); + $types = get_registered_entity_types(); - if (is_array($type)) { - $tempwhere = ""; - if (sizeof($type)) { - foreach ($type as $typekey => $subtypearray) { - foreach ($subtypearray as $subtypeval) { - $typekey = sanitise_string($typekey); - if (!empty($subtypeval)) { - if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) { - return false; - } - } else { - $subtypeval = 0; - } - if (!empty($tempwhere)) { - $tempwhere .= " or "; - } - $tempwhere .= "(e.type = '{$typekey}' and e.subtype = {$subtypeval})"; + foreach ($types as $type => $subtype_array) { + if (in_array($type, $options['allowed_types']) || $options['allowed_types'] === TRUE) { + // you must explicitly register types to show up in here and in search for objects + if ($type == 'object') { + if (is_array($subtype_array) && count($subtype_array)) { + $options['type_subtype_pairs'][$type] = $subtype_array; } - } - } - if (!empty($tempwhere)) { - $where[] = "({$tempwhere})"; - } - } else { - $type = sanitise_string($type); - if ($subtype AND !$subtype = get_subtype_id($type, $subtype)) { - return false; - } - - if ($type != "") { - $where[] = "e.type='$type'"; - } - if ($subtype !== "") { - $where[] = "e.subtype=$subtype"; - } - } - - if ($owner_guid != "") { - if (!is_array($owner_guid)) { - $owner_array = array($owner_guid); - $owner_guid = (int) $owner_guid; - } else if (sizeof($owner_guid) > 0) { - $owner_array = array_map('sanitise_int', $owner_guid); - } - if (is_null($container_guid)) { - $container_guid = $owner_array; - } - } - - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; - } - - if (!is_null($container_guid)) { - if (is_array($container_guid)) { - foreach ($container_guid as $key => $val) { - $container_guid[$key] = (int) $val; - } - $where[] = "e.container_guid in (" . implode(",", $container_guid) . ")"; - } else { - $container_guid = (int) $container_guid; - $where[] = "e.container_guid = {$container_guid}"; - } - } - - if ($name != "") { - $where[] = "s.name = '$name'"; - } - - if ($value != "") { - $where[] = "s.value='$value'"; - } - - if (!$count) { - $query = "SELECT distinct e.* - from {$CONFIG->dbprefix}entities e - JOIN {$CONFIG->dbprefix}private_settings s ON e.guid=s.entity_guid where "; - } else { - $query = "SELECT count(distinct e.guid) as total - from {$CONFIG->dbprefix}entities e JOIN {$CONFIG->dbprefix}private_settings s - ON e.guid=s.entity_guid where "; - } - foreach ($where as $w) { - $query .= " $w and "; - } - // Add access controls - $query .= get_access_sql_suffix('e'); - if (!$count) { - $query .= " order by $order_by"; - if ($limit) { - // Add order and limit - $query .= " limit $offset, $limit"; - } - - $dt = get_data($query, "entity_row_to_elggstar"); - return $dt; - } else { - $total = get_data_row($query); - return $total->total; - } -} - -/** - * Get entities based on their private data by multiple keys. - * - * @param string $name The name of the setting - * @param mixed $type Entity type - * @param string $subtype Entity subtype - * @param int $owner_guid The GUID of the owning user - * @param string $order_by The field to order by; by default, time_created desc - * @param int $limit The number of entities to return; 10 by default - * @param int $offset The indexing offset, 0 by default - * @param bool $count Count entities - * @param int $site_guid Site GUID. 0 for current, -1 for any. - * @param mixed $container_guid Container GUID - * - * @return array A list of entities. - * @todo deprecate - */ -function get_entities_from_private_setting_multi(array $name, $type = "", $subtype = "", -$owner_guid = 0, $order_by = "", $limit = 10, $offset = 0, $count = false, -$site_guid = 0, $container_guid = null) { - - global $CONFIG; - - if ($subtype === false || $subtype === null || $subtype === 0) { - return false; - } - - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - $order_by = sanitise_string($order_by); - $limit = (int)$limit; - $offset = (int)$offset; - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $where = array(); - - if (is_array($type)) { - $tempwhere = ""; - if (sizeof($type)) { - foreach ($type as $typekey => $subtypearray) { - foreach ($subtypearray as $subtypeval) { - $typekey = sanitise_string($typekey); - if (!empty($subtypeval)) { - if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) { - return false; - } - } else { - $subtypeval = 0; - } - if (!empty($tempwhere)) { - $tempwhere .= " or "; - } - $tempwhere .= "(e.type = '{$typekey}' and e.subtype = {$subtypeval})"; + } else { + if (is_array($subtype_array) && count($subtype_array)) { + $options['type_subtype_pairs'][$type] = $subtype_array; + } else { + $options['type_subtype_pairs'][$type] = ELGG_ENTITIES_ANY_VALUE; } } } - if (!empty($tempwhere)) { - $where[] = "({$tempwhere})"; - } - - } else { - $type = sanitise_string($type); - if ($subtype AND !$subtype = get_subtype_id($type, $subtype)) { - return false; - } - - if ($type != "") { - $where[] = "e.type='$type'"; - } - - if ($subtype !== "") { - $where[] = "e.subtype=$subtype"; - } - } - - if ($owner_guid != "") { - if (!is_array($owner_guid)) { - $owner_array = array($owner_guid); - $owner_guid = (int) $owner_guid; - } else if (sizeof($owner_guid) > 0) { - $owner_array = array_map('sanitise_int', $owner_guid); - } - if (is_null($container_guid)) { - $container_guid = $owner_array; - } - } - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; - } - - if (!is_null($container_guid)) { - if (is_array($container_guid)) { - foreach ($container_guid as $key => $val) { - $container_guid[$key] = (int) $val; - } - $where[] = "e.container_guid in (" . implode(",", $container_guid) . ")"; - } else { - $container_guid = (int) $container_guid; - $where[] = "e.container_guid = {$container_guid}"; - } } - if ($name) { - $s_join = ""; - $i = 1; - foreach ($name as $k => $n) { - $k = sanitise_string($k); - $n = sanitise_string($n); - $s_join .= " JOIN {$CONFIG->dbprefix}private_settings s$i ON e.guid=s$i.entity_guid"; - $where[] = "s$i.name = '$k'"; - $where[] = "s$i.value = '$n'"; - $i++; - } - } - - if (!$count) { - $query = "SELECT distinct e.* from {$CONFIG->dbprefix}entities e $s_join where "; + if (!empty($options['type_subtype_pairs'])) { + $count = elgg_get_entities(array_merge(array('count' => TRUE), $options)); + $entities = elgg_get_entities($options); } else { - $query = "SELECT count(distinct e.guid) as total - from {$CONFIG->dbprefix}entities e $s_join where "; + $count = 0; + $entities = array(); } - foreach ($where as $w) { - $query .= " $w and "; - } - - // Add access controls - $query .= get_access_sql_suffix('e'); - - if (!$count) { - $query .= " order by $order_by"; - // Add order and limit - if ($limit) { - $query .= " limit $offset, $limit"; - } - - $dt = get_data($query, "entity_row_to_elggstar"); - return $dt; - } else { - $total = get_data_row($query); - return $total->total; - } -} - -/** - * Gets a private setting for an entity. - * - * Plugin authors can set private data on entities. By default - * private data will not be searched or exported. - * - * @internal Private data is used to store settings for plugins - * and user settings. - * - * @param int $entity_guid The entity GUID - * @param string $name The name of the setting - * - * @return mixed The setting value, or false on failure - * @see set_private_setting() - * @see get_all_private_settings() - * @see remove_private_setting() - * @see remove_all_private_settings() - * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings - */ -function get_private_setting($entity_guid, $name) { - global $CONFIG; - $entity_guid = (int) $entity_guid; - $name = sanitise_string($name); - - $query = "SELECT value from {$CONFIG->dbprefix}private_settings - where name = '{$name}' and entity_guid = {$entity_guid}"; - $setting = get_data_row($query); - - if ($setting) { - return $setting->value; - } - return false; -} - -/** - * Return an array of all private settings. - * - * @param int $entity_guid The entity GUID - * - * @return array|false - * @see set_private_setting() - * @see get_private_settings() - * @see remove_private_setting() - * @see remove_all_private_settings() - * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings - */ -function get_all_private_settings($entity_guid) { - global $CONFIG; - - $entity_guid = (int) $entity_guid; - - $query = "SELECT * from {$CONFIG->dbprefix}private_settings where entity_guid = {$entity_guid}"; - $result = get_data($query); - if ($result) { - $return = array(); - foreach ($result as $r) { - $return[$r->name] = $r->value; - } - - return $return; - } - - return false; -} - -/** - * Sets a private setting for an entity. - * - * @param int $entity_guid The entity GUID - * @param string $name The name of the setting - * @param string $value The value of the setting - * - * @return mixed The setting ID, or false on failure - * @see get_private_setting() - * @see get_all_private_settings() - * @see remove_private_setting() - * @see remove_all_private_settings() - * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings - */ -function set_private_setting($entity_guid, $name, $value) { - global $CONFIG; - - $entity_guid = (int) $entity_guid; - $name = sanitise_string($name); - $value = sanitise_string($value); - - $result = insert_data("INSERT into {$CONFIG->dbprefix}private_settings - (entity_guid, name, value) VALUES - ($entity_guid, '{$name}', '{$value}') - ON DUPLICATE KEY UPDATE value='$value'"); - if ($result === 0) { - return true; - } - return $result; -} - -/** - * Deletes a private setting for an entity. - * - * @param int $entity_guid The Entity GUID - * @param string $name The name of the setting - * - * @return true|false depending on success - * @see get_private_setting() - * @see get_all_private_settings() - * @see set_private_setting() - * @see remove_all_private_settings() - * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings - */ -function remove_private_setting($entity_guid, $name) { - global $CONFIG; - - $entity_guid = (int) $entity_guid; - $name = sanitise_string($name); - - return delete_data("DELETE from {$CONFIG->dbprefix}private_settings - where name = '{$name}' - and entity_guid = {$entity_guid}"); -} - -/** - * Deletes all private settings for an entity. - * - * @param int $entity_guid The Entity GUID - * - * @return true|false depending on success - * @see get_private_setting() - * @see get_all_private_settings() - * @see set_private_setting() - * @see remove_private_settings() - * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings - */ -function remove_all_private_settings($entity_guid) { - global $CONFIG; - - $entity_guid = (int) $entity_guid; - return delete_data("DELETE from {$CONFIG->dbprefix}private_settings - where entity_guid = {$entity_guid}"); -} - -/** - * Check the recursive delete permissions token. - * - * If an entity is deleted recursively, a permissions override is required to allow - * contained or owned entities to be removed. - * - * @access private - * @return bool - * @elgg_plugin_hook_handler permissions_check all - * @elgg_plugin_hook_handler permissions_check:metadata all - */ -function recursive_delete_permissions_check() { - static $__RECURSIVE_DELETE_TOKEN; - - if ((isloggedin()) && ($__RECURSIVE_DELETE_TOKEN) - && (strcmp($__RECURSIVE_DELETE_TOKEN, md5(get_loggedin_userid())))) { - return true; - } - - // consult next function - return NULL; + $options['count'] = $count; + return elgg_view_entity_list($entities, $options); } /** @@ -2933,13 +2462,14 @@ function recursive_delete_permissions_check() { * @param string $subtype Entity subtype * @param string $class Class name * - * @return Bool - * @since 1.8 + * @return bool + * @since 1.8.0 */ function elgg_instanceof($entity, $type = NULL, $subtype = NULL, $class = NULL) { $return = ($entity instanceof ElggEntity); if ($type) { + /* @var ElggEntity $entity */ $return = $return && ($entity->getType() == $type); } @@ -2954,12 +2484,9 @@ function elgg_instanceof($entity, $type = NULL, $subtype = NULL, $class = NULL) return $return; } - /** * Update the last_action column in the entities table for $guid. * - * This determines the sort order of 1.8's default river. - * * @warning This is different to time_updated. Time_updated is automatically set, * while last_action is only set when explicitly called. * @@ -2967,10 +2494,12 @@ function elgg_instanceof($entity, $type = NULL, $subtype = NULL, $class = NULL) * @param int $posted Timestamp of last action * * @return bool - **/ + * @access private + */ function update_entity_last_action($guid, $posted = NULL) { global $CONFIG; $guid = (int)$guid; + $posted = (int)$posted; if (!$posted) { $posted = time(); @@ -2995,27 +2524,36 @@ function update_entity_last_action($guid, $posted = NULL) { * * @return void * @elgg_plugin_hook_handler gc system + * @access private */ function entities_gc() { global $CONFIG; - $tables = array ('sites_entity', 'objects_entity', 'groups_entity', 'users_entity'); + $tables = array( + 'site' => 'sites_entity', + 'object' => 'objects_entity', + 'group' => 'groups_entity', + 'user' => 'users_entity' + ); - foreach ($tables as $table) { - delete_data("DELETE from {$CONFIG->dbprefix}{$table} - where guid NOT IN (SELECT guid from {$CONFIG->dbprefix}entities)"); + foreach ($tables as $type => $table) { + delete_data("DELETE FROM {$CONFIG->dbprefix}{$table} + WHERE guid NOT IN (SELECT guid FROM {$CONFIG->dbprefix}entities)"); + delete_data("DELETE FROM {$CONFIG->dbprefix}entities + WHERE type = '$type' AND guid NOT IN (SELECT guid FROM {$CONFIG->dbprefix}{$table})"); } } /** * Runs unit tests for the entity objects. * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params * * @return array + * @access private */ function entities_test($hook, $type, $value, $params) { global $CONFIG; @@ -3028,31 +2566,25 @@ function entities_test($hook, $type, $value, $params) { * * @return void * @elgg_event_handler init system + * @access private */ function entities_init() { - register_page_handler('view', 'entities_page_handler'); - - register_plugin_hook('unit_test', 'system', 'entities_test'); + elgg_register_page_handler('view', 'entities_page_handler'); - // Allow a permission override for recursive entity deletion - // @todo Can this be done better? - register_plugin_hook('permissions_check', 'all', 'recursive_delete_permissions_check'); - register_plugin_hook('permissions_check:metadata', 'all', 'recursive_delete_permissions_check'); + elgg_register_plugin_hook_handler('unit_test', 'system', 'entities_test'); - register_plugin_hook('gc', 'system', 'entities_gc'); + elgg_register_plugin_hook_handler('gc', 'system', 'entities_gc'); } /** Register the import hook */ -register_plugin_hook("import", "all", "import_entity_plugin_hook", 0); +elgg_register_plugin_hook_handler("import", "all", "import_entity_plugin_hook", 0); /** Register the hook, ensuring entities are serialised first */ -register_plugin_hook("export", "all", "export_entity_plugin_hook", 0); +elgg_register_plugin_hook_handler("export", "all", "export_entity_plugin_hook", 0); /** Hook to get certain named bits of volatile data about an entity */ -register_plugin_hook('volatile', 'metadata', 'volatile_data_export_plugin_hook'); - -/** Hook for rendering a default icon for entities */ -register_plugin_hook('entity:icon:url', 'all', 'default_entity_icon_hook', 1000); +elgg_register_plugin_hook_handler('volatile', 'metadata', 'volatile_data_export_plugin_hook'); /** Register init system event **/ -register_elgg_event_handler('init', 'system', 'entities_init');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'entities_init'); + diff --git a/engine/lib/export.php b/engine/lib/export.php index 3a5c74a0f..ecc894e63 100644 --- a/engine/lib/export.php +++ b/engine/lib/export.php @@ -11,7 +11,7 @@ * * @param mixed $object The object either an ElggEntity, ElggRelationship or ElggExtender * - * @return the UUID or false + * @return string|false the UUID or false */ function get_uuid_from_object($object) { if ($object instanceof ElggEntity) { @@ -40,8 +40,6 @@ function get_uuid_from_object($object) { * @return string */ function guid_to_uuid($guid) { - global $CONFIG; - return elgg_get_site_url() . "export/opendd/$guid/"; } @@ -53,8 +51,6 @@ function guid_to_uuid($guid) { * @return bool */ function is_uuid_this_domain($uuid) { - global $CONFIG; - if (strpos($uuid, elgg_get_site_url()) === 0) { return true; } @@ -67,7 +63,7 @@ function is_uuid_this_domain($uuid) { * * @param string $uuid A unique ID * - * @return mixed + * @return ElggEntity|false */ function get_entity_from_uuid($uuid) { $uuid = sanitise_string($uuid); @@ -111,23 +107,25 @@ $IMPORTED_OBJECT_COUNTER = 0; * @param ODD $odd The odd element to process * * @return bool + * @access private */ function _process_element(ODD $odd) { global $IMPORTED_DATA, $IMPORTED_OBJECT_COUNTER; // See if anyone handles this element, return true if it is. + $to_be_serialised = null; if ($odd) { - $handled = trigger_plugin_hook("import", "all", array("element" => $odd), $to_be_serialised); - } + $handled = elgg_trigger_plugin_hook("import", "all", array("element" => $odd), $to_be_serialised); - // If not, then see if any of its sub elements are handled - if ($handled) { - // Increment validation counter - $IMPORTED_OBJECT_COUNTER ++; - // Return the constructed object - $IMPORTED_DATA[] = $handled; + // If not, then see if any of its sub elements are handled + if ($handled) { + // Increment validation counter + $IMPORTED_OBJECT_COUNTER ++; + // Return the constructed object + $IMPORTED_DATA[] = $handled; - return true; + return true; + } } return false; @@ -140,16 +138,17 @@ function _process_element(ODD $odd) { * * @return array * @throws ExportException + * @access private */ function exportAsArray($guid) { $guid = (int)$guid; // Trigger a hook to - $to_be_serialised = trigger_plugin_hook("export", "all", array("guid" => $guid), array()); + $to_be_serialised = elgg_trigger_plugin_hook("export", "all", array("guid" => $guid), array()); // Sanity check if ((!is_array($to_be_serialised)) || (count($to_be_serialised) == 0)) { - throw new ExportException(sprintf(elgg_echo('ExportException:NoSuchEntity'), $guid)); + throw new ExportException(elgg_echo('ExportException:NoSuchEntity', array($guid))); } return $to_be_serialised; @@ -165,8 +164,9 @@ function exportAsArray($guid) { * * @param int $guid The GUID. * - * @return xml + * @return string XML * @see ElggEntity for an example of its usage. + * @access private */ function export($guid) { $odd = new ODDDocument(exportAsArray($guid)); @@ -181,7 +181,8 @@ function export($guid) { * @param string $xml XML string * * @return bool - * @throws Exception if there was a problem importing the data. + * @throws ImportException if there was a problem importing the data. + * @access private */ function import($xml) { global $IMPORTED_DATA, $IMPORTED_OBJECT_COUNTER; @@ -210,12 +211,13 @@ function import($xml) { * Register the OpenDD import action * * @return void + * @access private */ function export_init() { global $CONFIG; - register_action("import/opendd", false); + elgg_register_action("import/opendd"); } // Register a startup event -register_elgg_event_handler('init', 'system', 'export_init', 100); +elgg_register_event_handler('init', 'system', 'export_init', 100); diff --git a/engine/lib/extender.php b/engine/lib/extender.php index 845cfd85f..8323bd3ce 100644 --- a/engine/lib/extender.php +++ b/engine/lib/extender.php @@ -19,7 +19,7 @@ * @return string */ function detect_extender_valuetype($value, $value_type = "") { - if ($value_type != "") { + if ($value_type != "" && ($value_type == 'integer' || $value_type == 'text')) { return $value_type; } @@ -44,6 +44,7 @@ function detect_extender_valuetype($value, $value_type = "") { * @param ODDMetaData $element The OpenDD element * * @return bool + * @access private */ function oddmetadata_to_elggextender(ElggEntity $entity, ODDMetaData $element) { // Get the type of extender (metadata, type, attribute etc) @@ -85,6 +86,7 @@ function oddmetadata_to_elggextender(ElggEntity $entity, ODDMetaData $element) { * @return null * @elgg_plugin_hook_handler volatile metadata * @todo investigate more. + * @throws ImportException * @access private */ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) { @@ -93,18 +95,20 @@ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) $tmp = NULL; if ($element instanceof ODDMetaData) { + /* @var ODDMetaData $element */ // Recall entity $entity_uuid = $element->getAttribute('entity_uuid'); $entity = get_entity_from_uuid($entity_uuid); if (!$entity) { - throw new ImportException(sprintf(elgg_echo('ImportException:GUIDNotFound'), $entity_uuid)); + throw new ImportException(elgg_echo('ImportException:GUIDNotFound', array($entity_uuid))); } oddmetadata_to_elggextender($entity, $element); // Save if (!$entity->save()) { - $msg = sprintf(elgg_echo('ImportException:ProblemUpdatingMeta'), $attr_name, $entity_uuid); + $attr_name = $element->getAttribute('name'); + $msg = elgg_echo('ImportException:ProblemUpdatingMeta', array($attr_name, $entity_uuid)); throw new ImportException($msg); } @@ -119,62 +123,68 @@ function import_extender_plugin_hook($hook, $entity_type, $returnvalue, $params) * @param string $type 'metadata' or 'annotation' * @param int $user_guid The GUID of the user * - * @return true|false + * @return bool */ function can_edit_extender($extender_id, $type, $user_guid = 0) { - if (!isloggedin()) { - return false; + // @todo Since Elgg 1.0, Elgg has returned false from can_edit_extender() + // if no user was logged in. This breaks the access override. This is a + // temporary work around. This function needs to be rewritten in Elgg 1.9 + if (!elgg_check_access_overrides($user_guid)) { + if (!elgg_is_logged_in()) { + return false; + } } $user_guid = (int)$user_guid; - $user = get_entity($user_guid); + $user = get_user($user_guid); if (!$user) { - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); + $user_guid = elgg_get_logged_in_user_guid(); } - $functionname = "get_{$type}"; + $functionname = "elgg_get_{$type}_from_id"; if (is_callable($functionname)) { - $extender = $functionname($extender_id); + $extender = call_user_func($functionname, $extender_id); } else { return false; } - if (!is_a($extender, "ElggExtender")) { + if (!($extender instanceof ElggExtender)) { return false; } + /* @var ElggExtender $extender */ // If the owner is the specified user, great! They can edit. - if ($extender->getOwner() == $user->getGUID()) { + if ($extender->getOwnerGUID() == $user_guid) { return true; } // If the user can edit the entity this is attached to, great! They can edit. - if (can_edit_entity($extender->entity_guid, $user->getGUID())) { + if (can_edit_entity($extender->entity_guid, $user_guid)) { return true; } - // Trigger plugin hooks - $params = array('entity' => $entity, 'user' => $user); - return trigger_plugin_hook('permissions_check', $type, $params, false); + // Trigger plugin hook - note that $user may be null + $params = array('entity' => $extender->getEntity(), 'user' => $user); + return elgg_trigger_plugin_hook('permissions_check', $type, $params, false); } /** * Sets the URL handler for a particular extender type and name. * It is recommended that you do not call this directly, instead use - * one of the wrapper functions in the subtype files. + * one of the wrapper functions such as elgg_register_annotation_url_handler(). * - * @param string $function_name The function to register - * @param string $extender_type Extender type + * @param string $extender_type Extender type ('annotation', 'metadata') * @param string $extender_name The name of the extender + * @param string $function_name The function to register * - * @return true|false Depending on success + * @return bool */ -function register_extender_url_handler($function_name, $extender_type = "all", -$extender_name = "all") { +function elgg_register_extender_url_handler($extender_type, $extender_name, $function_name) { global $CONFIG; - if (!is_callable($function_name)) { + if (!is_callable($function_name, true)) { return false; } @@ -221,19 +231,19 @@ function get_extender_url(ElggExtender $extender) { } if (is_callable($function)) { - $url = $function($extender); + $url = call_user_func($function, $extender); } if ($url == "") { $nameid = $extender->id; if ($type == 'volatile') { - $nameid == $extender->name; + $nameid = $extender->name; } $url = "export/$view/$guid/$type/$nameid/"; } - + return elgg_normalize_url($url); } /** Register the hook */ -register_plugin_hook("import", "all", "import_extender_plugin_hook", 2);
\ No newline at end of file +elgg_register_plugin_hook_handler("import", "all", "import_extender_plugin_hook", 2); diff --git a/engine/lib/filestore.php b/engine/lib/filestore.php index 23b10c24c..a3c7ba439 100644 --- a/engine/lib/filestore.php +++ b/engine/lib/filestore.php @@ -18,7 +18,7 @@ */ function get_dir_size($dir, $totalsize = 0) { $handle = @opendir($dir); - while ($file = @readdir ($handle)) { + while ($file = @readdir($handle)) { if (eregi("^\.{1,2}$", $file)) { continue; } @@ -149,6 +149,12 @@ $x1 = 0, $y1 = 0, $x2 = 0, $y2 = 0, $upscale = FALSE) { return FALSE; } + // color transparencies white (default is black) + imagefilledrectangle( + $new_image, 0, 0, $params['newwidth'], $params['newheight'], + imagecolorallocate($new_image, 255, 255, 255) + ); + $rtn_code = imagecopyresampled( $new_image, $original_image, 0, @@ -302,8 +308,6 @@ function get_image_resize_parameters($width, $height, $options) { function file_delete($guid) { if ($file = get_entity($guid)) { if ($file->canEdit()) { - $container = get_entity($file->container_guid); - $thumbnail = $file->thumbnail; $smallthumb = $file->smallthumb; $largethumb = $file->largethumb; @@ -376,8 +380,8 @@ function file_get_general_file_type($mimetype) { /** * Delete a directory and all its contents - *
- * @param str $directory Directory to delete
+ * + * @param string $directory Directory to delete * * @return bool */ @@ -465,6 +469,7 @@ function set_default_filestore(ElggFilestore $filestore) { * ElggFile. * * @return void + * @access private */ function filestore_run_once() { // Register a class @@ -473,16 +478,19 @@ function filestore_run_once() { /** * Initialise the file modules. - * Listens to system boot and registers any appropriate file types and classes + * Listens to system init and configures the default filestore * * @return void + * @access private */ function filestore_init() { global $CONFIG; // Now register a default filestore - set_default_filestore(new ElggDiskFilestore($CONFIG->dataroot)); - + if (isset($CONFIG->dataroot)) { + set_default_filestore(new ElggDiskFilestore($CONFIG->dataroot)); + } + // Now run this stuff, but only once run_function_once("filestore_run_once"); } @@ -490,12 +498,13 @@ function filestore_init() { /** * Unit tests for files * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params * * @return array + * @access private */ function filestore_test($hook, $type, $value, $params) { global $CONFIG; @@ -505,7 +514,7 @@ function filestore_test($hook, $type, $value, $params) { // Register a startup event -register_elgg_event_handler('init', 'system', 'filestore_init', 100); +elgg_register_event_handler('init', 'system', 'filestore_init', 100); // Unit testing -register_plugin_hook('unit_test', 'system', 'filestore_test');
\ No newline at end of file +elgg_register_plugin_hook_handler('unit_test', 'system', 'filestore_test'); diff --git a/engine/lib/group.php b/engine/lib/group.php index 31dc434ab..6ded8a825 100644 --- a/engine/lib/group.php +++ b/engine/lib/group.php @@ -14,6 +14,7 @@ * @param int $guid GUID for a group * * @return array|false + * @access private */ function get_group_entity_as_row($guid) { global $CONFIG; @@ -24,7 +25,7 @@ function get_group_entity_as_row($guid) { } /** - * Create or update the extras table for a given group. + * Create or update the entities table for a given group. * Call create_entity first. * * @param int $guid GUID @@ -32,6 +33,7 @@ function get_group_entity_as_row($guid) { * @param string $description Description * * @return bool + * @access private */ function create_group_entity($guid, $name, $description) { global $CONFIG; @@ -52,7 +54,7 @@ function create_group_entity($guid, $name, $description) { if ($result != false) { // Update succeeded, continue $entity = get_entity($guid); - if (trigger_elgg_event('update', $entity->type, $entity)) { + if (elgg_trigger_event('update', $entity->type, $entity)) { return $guid; } else { $entity->delete(); @@ -66,7 +68,7 @@ function create_group_entity($guid, $name, $description) { $result = insert_data($query); if ($result !== false) { $entity = get_entity($guid); - if (trigger_elgg_event('create', $entity->type, $entity)) { + if (elgg_trigger_event('create', $entity->type, $entity)) { return $guid; } else { $entity->delete(); @@ -79,23 +81,6 @@ function create_group_entity($guid, $name, $description) { } /** - * THIS FUNCTION IS DEPRECATED. - * - * Delete a group's extra data. - * - * @param int $guid The guid of the group - * - * @return bool - * @deprecated 1.6 - */ -function delete_group_entity($guid) { - elgg_deprecated_notice("delete_group_entity has been deprecated", 1.6); - - // Always return that we have deleted one row in order to not break existing code. - return 1; -} - -/** * Add an object to the given group. * * @param int $group_guid The group to add the object to. @@ -116,12 +101,12 @@ function add_object_to_group($group_guid, $object_guid) { } if (!($group instanceof ElggGroup)) { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $group_guid, 'ElggGroup'); + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($group_guid, 'ElggGroup')); throw new InvalidClassException($msg); } if (!($object instanceof ElggObject)) { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $object_guid, 'ElggObject'); + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($object_guid, 'ElggObject')); throw new InvalidClassException($msg); } @@ -150,12 +135,12 @@ function remove_object_from_group($group_guid, $object_guid) { } if (!($group instanceof ElggGroup)) { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $group_guid, 'ElggGroup'); + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($group_guid, 'ElggGroup')); throw new InvalidClassException($msg); } if (!($object instanceof ElggObject)) { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $object_guid, 'ElggObject'); + $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($object_guid, 'ElggObject')); throw new InvalidClassException($msg); } @@ -164,321 +149,6 @@ function remove_object_from_group($group_guid, $object_guid) { } /** - * Return an array of objects in a given container. - * - * @see get_entities() - * - * @param int $group_guid The container (defaults to current page owner) - * @param string $subtype The subtype - * @param int $owner_guid Owner - * @param int $site_guid The site - * @param string $order_by Order - * @param int $limit Limit on number of elements to return, by default 10. - * @param int $offset Where to start, by default 0. - * @param bool $count Whether to return the entities or a count of them. - * - * @return array|false - */ -function get_objects_in_group($group_guid, $subtype = "", $owner_guid = 0, $site_guid = 0, -$order_by = "", $limit = 10, $offset = 0, $count = FALSE) { - - global $CONFIG; - - if ($subtype === FALSE || $subtype === null || $subtype === 0) { - return FALSE; - } - - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - $order_by = sanitise_string($order_by); - $limit = (int)$limit; - $offset = (int)$offset; - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $container_guid = (int)$group_guid; - if ($container_guid == 0) { - $container_guid = elgg_get_page_owner_guid(); - } - - $where = array(); - - $where[] = "e.type='object'"; - - if (!empty($subtype)) { - if (!$subtype = get_subtype_id('object', $subtype)) { - return FALSE; - } - $where[] = "e.subtype=$subtype"; - } - if ($owner_guid != "") { - if (!is_array($owner_guid)) { - $owner_guid = (int) $owner_guid; - $where[] = "e.container_guid = '$owner_guid'"; - } else if (sizeof($owner_guid) > 0) { - // Cast every element to the owner_guid array to int - $owner_guid = array_map("sanitise_int", $owner_guid); - $owner_guid = implode(",", $owner_guid); - $where[] = "e.container_guid in ({$owner_guid})"; - } - } - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; - } - - if ($container_guid > 0) { - $where[] = "e.container_guid = {$container_guid}"; - } - - if (!$count) { - $query = "SELECT * from {$CONFIG->dbprefix}entities e" - . " join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid where "; - } else { - $query = "SELECT count(e.guid) as total from {$CONFIG->dbprefix}entities e" - . " join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid where "; - } - foreach ($where as $w) { - $query .= " $w and "; - } - - // Add access controls - $query .= get_access_sql_suffix('e'); - if (!$count) { - $query .= " order by $order_by"; - - // Add order and limit - if ($limit) { - $query .= " limit $offset, $limit"; - } - - $dt = get_data($query, "entity_row_to_elggstar"); - return $dt; - } else { - $total = get_data_row($query); - return $total->total; - } -} - -/** - * Get all the entities from metadata from a group. - * - * @param int $group_guid The ID of the group. - * @param mixed $meta_name Metadata name - * @param mixed $meta_value Metadata value - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $owner_guid Owner guid - * @param int $limit Limit - * @param int $offset Offset - * @param string $order_by Optional ordering. - * @param int $site_guid Site GUID. 0 for current, -1 for any - * @param bool $count Return count instead of entities - * - * @return array|false - * @deprecated 1.8 Use elgg_get_entities_from_metadata() - */ -function get_entities_from_metadata_groups($group_guid, $meta_name, $meta_value = "", -$entity_type = "", $entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, -$order_by = "", $site_guid = 0, $count = false) { - elgg_deprecated_notice("get_entities_from_metadata_groups was deprecated in 1.8.", 1.8); - global $CONFIG; - - $meta_n = get_metastring_id($meta_name); - $meta_v = get_metastring_id($meta_value); - - $entity_type = sanitise_string($entity_type); - $entity_subtype = get_subtype_id($entity_type, $entity_subtype); - $limit = (int)$limit; - $offset = (int)$offset; - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - $order_by = sanitise_string($order_by); - $site_guid = (int) $site_guid; - if (is_array($owner_guid)) { - foreach ($owner_guid as $key => $guid) { - $owner_guid[$key] = (int) $guid; - } - } else { - $owner_guid = (int) $owner_guid; - } - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $container_guid = (int)$group_guid; - if ($container_guid == 0) { - $container_guid = elgg_get_page_owner_guid(); - } - - $where = array(); - - if ($entity_type != "") { - $where[] = "e.type='$entity_type'"; - } - if ($entity_subtype) { - $where[] = "e.subtype=$entity_subtype"; - } - if ($meta_name != "") { - $where[] = "m.name_id='$meta_n'"; - } - if ($meta_value != "") { - $where[] = "m.value_id='$meta_v'"; - } - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; - } - if ($container_guid > 0) { - $where[] = "e.container_guid = {$container_guid}"; - } - - if (is_array($owner_guid)) { - $where[] = "e.container_guid in (" . implode(",", $owner_guid ) . ")"; - } else if ($owner_guid > 0) { - $where[] = "e.container_guid = {$owner_guid}"; - } - - if (!$count) { - $query = "SELECT distinct e.* "; - } else { - $query = "SELECT count(e.guid) as total "; - } - - $query .= "from {$CONFIG->dbprefix}entities e" - . " JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid " - . " JOIN {$CONFIG->dbprefix}objects_entity o on e.guid = o.guid where"; - - foreach ($where as $w) { - $query .= " $w and "; - } - - // Add access controls - $query .= get_access_sql_suffix("e"); - - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($row = get_data_row($query)) { - return $row->total; - } - } - return false; -} - -/** - * As get_entities_from_metadata_groups() but with multiple entities. - * - * @param int $group_guid The ID of the group. - * @param array $meta_array Array of 'name' => 'value' pairs - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $owner_guid Owner GUID - * @param int $limit Limit - * @param int $offset Offset - * @param string $order_by Optional ordering. - * @param int $site_guid Site GUID. 0 for current, -1 for any - * @param bool $count Return count of entities instead of entities - * - * @return int|array List of ElggEntities, or the total number if count is set to false - * @deprecated 1.8 Use elgg_get_entities_from_metadata() - */ -function get_entities_from_metadata_groups_multi($group_guid, $meta_array, $entity_type = "", -$entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", -$site_guid = 0, $count = false) { - elgg_deprecated_notice("get_entities_from_metadata_groups_multi was deprecated in 1.8.", 1.8); - - global $CONFIG; - - if (!is_array($meta_array) || sizeof($meta_array) == 0) { - return false; - } - - $where = array(); - - $mindex = 1; - $join = ""; - foreach ($meta_array as $meta_name => $meta_value) { - $meta_n = get_metastring_id($meta_name); - $meta_v = get_metastring_id($meta_value); - $join .= " JOIN {$CONFIG->dbprefix}metadata m{$mindex} on e.guid = m{$mindex}.entity_guid" - . " JOIN {$CONFIG->dbprefix}objects_entity o on e.guid = o.guid "; - - if ($meta_name != "") { - $where[] = "m{$mindex}.name_id='$meta_n'"; - } - - if ($meta_value != "") { - $where[] = "m{$mindex}.value_id='$meta_v'"; - } - - $mindex++; - } - - $entity_type = sanitise_string($entity_type); - $entity_subtype = get_subtype_id($entity_type, $entity_subtype); - $limit = (int)$limit; - $offset = (int)$offset; - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - $order_by = sanitise_string($order_by); - $owner_guid = (int) $owner_guid; - - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - //$access = get_access_list(); - - if ($entity_type != "") { - $where[] = "e.type = '{$entity_type}'"; - } - - if ($entity_subtype) { - $where[] = "e.subtype = {$entity_subtype}"; - } - - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; - } - - if ($owner_guid > 0) { - $where[] = "e.owner_guid = {$owner_guid}"; - } - - if ($container_guid > 0) { - $where[] = "e.container_guid = {$container_guid}"; - } - - if ($count) { - $query = "SELECT count(e.guid) as total "; - } else { - $query = "SELECT distinct e.* "; - } - - $query .= " from {$CONFIG->dbprefix}entities e {$join} where"; - foreach ($where as $w) { - $query .= " $w and "; - } - $query .= get_access_sql_suffix("e"); // Add access controls - - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } - } - return false; -} - -/** * Return a list of this group's members. * * @param int $group_guid The ID of the container/group. @@ -500,7 +170,7 @@ function get_group_members($group_guid, $limit = 10, $offset = 0, $site_guid = 0 'relationship' => 'member', 'relationship_guid' => $group_guid, 'inverse_relationship' => TRUE, - 'types' => 'user', + 'type' => 'user', 'limit' => $limit, 'offset' => $offset, 'count' => $count, @@ -528,16 +198,18 @@ function is_group_member($group_guid, $user_guid) { /** * Join a user to a group. * - * @param int $group_guid The group. - * @param int $user_guid The user. + * @param int $group_guid The group GUID. + * @param int $user_guid The user GUID. * * @return bool */ function join_group($group_guid, $user_guid) { $result = add_entity_relationship($user_guid, 'member', $group_guid); - $param = array('group' => get_entity($group_guid), 'user' => get_entity($user_guid)); - trigger_elgg_event('join', 'group', $params); + if ($result) { + $params = array('group' => get_entity($group_guid), 'user' => get_entity($user_guid)); + elgg_trigger_event('join', 'group', $params); + } return $result; } @@ -554,7 +226,7 @@ function leave_group($group_guid, $user_guid) { // event needs to be triggered while user is still member of group to have access to group acl $params = array('group' => get_entity($group_guid), 'user' => get_entity($user_guid)); - trigger_elgg_event('leave', 'group', $params); + elgg_trigger_event('leave', 'group', $params); $result = remove_entity_relationship($user_guid, 'member', $group_guid); return $result; } @@ -568,59 +240,65 @@ function leave_group($group_guid, $user_guid) { */ function get_users_membership($user_guid) { $options = array( + 'type' => 'group', 'relationship' => 'member', 'relationship_guid' => $user_guid, - 'inverse_relationship' => FALSE + 'inverse_relationship' => false, + 'limit' => false, ); return elgg_get_entities_from_relationship($options); } /** - * Checks access to a group. + * May the current user access item(s) on this page? If the page owner is a group, + * membership, visibility, and logged in status are taken into account. * * @param boolean $forward If set to true (default), will forward the page; * if set to false, will return true or false. * - * @return true|false If $forward is set to false. + * @return bool If $forward is set to false. */ function group_gatekeeper($forward = true) { - $allowed = true; - $url = ''; - - if ($group = elgg_get_page_owner()) { - if ($group instanceof ElggGroup) { - $url = $group->getURL(); - if ( - ((!isloggedin()) && (!$group->isPublicMembership())) || - ((!$group->isMember(get_loggedin_user()) && (!$group->isPublicMembership()))) - ) { - $allowed = false; - } - // Admin override - if (isadminloggedin()) { - $allowed = true; - } - } + $page_owner_guid = elgg_get_page_owner_guid(); + if (!$page_owner_guid) { + return true; + } + $visibility = ElggGroupItemVisibility::factory($page_owner_guid); + + if (!$visibility->shouldHideItems) { + return true; } + if ($forward) { + // only forward to group if user can see it + $group = get_entity($page_owner_guid); + $forward_url = $group ? $group->getURL() : ''; - if ($forward && $allowed == false) { - register_error(elgg_echo('membershiprequired')); - forward($url); - exit; + if (!elgg_is_logged_in()) { + $_SESSION['last_forward_from'] = current_page_url(); + $forward_reason = 'login'; + } else { + $forward_reason = 'member'; + } + + register_error(elgg_echo($visibility->reasonHidden)); + forward($forward_url, $forward_reason); } - return $allowed; + return false; } /** - * Manages group tool options + * Adds a group tool option + * + * @see remove_group_tool_option(). * - * @param string $name Name of the group tool option - * @param string $label Used for the group edit form - * @param boolean $default_on True if this option should be active by default + * @param string $name Name of the group tool option + * @param string $label Used for the group edit form + * @param bool $default_on True if this option should be active by default * * @return void + * @since 1.5.0 */ function add_group_tool_option($name, $label, $default_on = true) { global $CONFIG; @@ -639,118 +317,25 @@ function add_group_tool_option($name, $label, $default_on = true) { } /** - * Searches for a group based on a complete or partial name or description + * Removes a group tool option based on name * - * @param string $criteria The partial or full name or description - * @param int $limit Limit of the search. - * @param int $offset Offset. - * @param string $order_by The order. - * @param boolean $count Whether to return the count of results or just the results. + * @see add_group_tool_option() * - * @return mixed - * @deprecated 1.7 + * @param string $name Name of the group tool option + * + * @return void + * @since 1.7.5 */ -function search_for_group($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { - elgg_deprecated_notice('search_for_group() was deprecated by new search plugin.', 1.7); +function remove_group_tool_option($name) { global $CONFIG; - $criteria = sanitise_string($criteria); - $limit = (int)$limit; - $offset = (int)$offset; - $order_by = sanitise_string($order_by); - - $access = get_access_sql_suffix("e"); - - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - - if ($count) { - $query = "SELECT count(e.guid) as total "; - } else { - $query = "SELECT e.* "; - } - $query .= "from {$CONFIG->dbprefix}entities e" - . " JOIN {$CONFIG->dbprefix}groups_entity g on e.guid=g.guid where "; - - $query .= "(g.name like \"%{$criteria}%\" or g.description like \"%{$criteria}%\")"; - $query .= " and $access"; - - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } + if (!isset($CONFIG->group_tool_options)) { + return; } - return false; -} - -/** - * Returns a formatted list of groups suitable for injecting into search. - * - * @deprecated 1.7 - * - * @param string $hook Hook name - * @param string $user User - * @param mixed $returnvalue Previous hook's return value - * @param string $tag Tag to search on - * - * @return string - */ -function search_list_groups_by_name($hook, $user, $returnvalue, $tag) { - elgg_deprecated_notice('search_list_groups_by_name() was deprecated by new search plugin', 1.7); - // Change this to set the number of groups that display on the search page - $threshold = 4; - - $object = get_input('object'); - - if (!get_input('offset') && (empty($object) || $object == 'group')) { - if ($groups = search_for_group($tag, $threshold)) { - $countgroups = search_for_group($tag, 0, 0, "", true); - $return = elgg_view('group/search/startblurb', array('count' => $countgroups, 'tag' => $tag)); - foreach ($groups as $group) { - $return .= elgg_view_entity($group); - } - $vars = array('count' => $countgroups, 'threshold' => $threshold, 'tag' => $tag); - $return .= elgg_view('group/search/finishblurb', $vars); - return $return; + foreach ($CONFIG->group_tool_options as $i => $option) { + if ($option->name == $name) { + unset($CONFIG->group_tool_options[$i]); } } } - -/** - * Displays a list of group objects that have been searched for. - * - * @see elgg_view_entity_list - * - * @param string $tag Search criteria - * @param int $limit The number of entities to display on a page - * - * @return string The list in a form suitable to display - * @deprecated 1.7 - */ -function list_group_search($tag, $limit = 10) { - elgg_deprecated_notice('list_group_search() was deprecated by new search plugin.', 1.7); - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = (int) search_for_group($tag, 10, 0, '', true); - $entities = search_for_group($tag, $limit, $offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, false); - -} - -/** - * Performs initialisation functions for groups - * - * @return void - */ -function group_init() { - // Register an entity type - register_entity_type('group', ''); -} - -register_elgg_event_handler('init', 'system', 'group_init'); diff --git a/engine/lib/input.php b/engine/lib/input.php index 4ba6f500c..80b0b8766 100644 --- a/engine/lib/input.php +++ b/engine/lib/input.php @@ -8,46 +8,51 @@ */ /** - * Get some input from variables passed on the GET or POST line. + * Get some input from variables passed submitted through GET or POST. + * + * If using any data obtained from get_input() in a web page, please be aware that + * it is a possible vector for a reflected XSS attack. If you are expecting an + * integer, cast it to an int. If it is a string, escape quotes. * * Note: this function does not handle nested arrays (ex: form input of param[m][n]) * because of the filtering done in htmlawed from the filter_tags call. + * @todo Is this ^ still true? * - * @param string $variable The variable we want to return. + * @param string $variable The variable name we want. * @param mixed $default A default value for the variable if it is not found. - * @param bool $filter_result If true then the result is filtered for bad tags. + * @param bool $filter_result If true, then the result is filtered for bad tags. * - * @return string + * @return mixed */ function get_input($variable, $default = NULL, $filter_result = TRUE) { global $CONFIG; + $result = $default; + + elgg_push_context('input'); + if (isset($CONFIG->input[$variable])) { - $var = $CONFIG->input[$variable]; + $result = $CONFIG->input[$variable]; if ($filter_result) { - $var = filter_tags($var); + $result = filter_tags($result); } - - return $var; - } - - if (isset($_REQUEST[$variable])) { + } elseif (isset($_REQUEST[$variable])) { if (is_array($_REQUEST[$variable])) { - $var = $_REQUEST[$variable]; + $result = $_REQUEST[$variable]; } else { - $var = trim($_REQUEST[$variable]); + $result = trim($_REQUEST[$variable]); } if ($filter_result) { - $var = filter_tags($var); + $result = filter_tags($result); } - - return $var; } - return $default; + elgg_pop_context(); + + return $result; } /** @@ -55,8 +60,8 @@ function get_input($variable, $default = NULL, $filter_result = TRUE) { * * Note: this function does not handle nested arrays (ex: form input of param[m][n]) * - * @param string $variable The name of the variable - * @param string $value The value of the variable + * @param string $variable The name of the variable + * @param string|string[] $value The value of the variable * * @return void */ @@ -83,7 +88,7 @@ function set_input($variable, $value) { * @return mixed The filtered result - everything will be strings */ function filter_tags($var) { - return trigger_plugin_hook('validate', 'input', null, $var); + return elgg_trigger_plugin_hook('validate', 'input', null, $var); } /** @@ -98,20 +103,151 @@ function is_email_address($address) { } /** + * Load all the REQUEST variables into the sticky form cache + * + * Call this from an action when you want all your submitted variables + * available if the submission fails validation and is sent back to the form + * + * @param string $form_name Name of the sticky form + * + * @return void + * @link http://docs.elgg.org/Tutorials/UI/StickyForms + * @since 1.8.0 + */ +function elgg_make_sticky_form($form_name) { + + elgg_clear_sticky_form($form_name); + + if (!isset($_SESSION['sticky_forms'])) { + $_SESSION['sticky_forms'] = array(); + } + $_SESSION['sticky_forms'][$form_name] = array(); + + foreach ($_REQUEST as $key => $var) { + // will go through XSS filtering on the get function + $_SESSION['sticky_forms'][$form_name][$key] = $var; + } +} + +/** + * Clear the sticky form cache + * + * Call this if validation is successful in the action handler or + * when they sticky values have been used to repopulate the form + * after a validation error. + * + * @param string $form_name Form namespace + * + * @return void + * @link http://docs.elgg.org/Tutorials/UI/StickyForms + * @since 1.8.0 + */ +function elgg_clear_sticky_form($form_name) { + unset($_SESSION['sticky_forms'][$form_name]); +} + +/** + * Has this form been made sticky? + * + * @param string $form_name Form namespace + * + * @return boolean + * @link http://docs.elgg.org/Tutorials/UI/StickyForms + * @since 1.8.0 + */ +function elgg_is_sticky_form($form_name) { + return isset($_SESSION['sticky_forms'][$form_name]); +} + +/** + * Get a specific sticky variable + * + * @param string $form_name The name of the form + * @param string $variable The name of the variable + * @param mixed $default Default value if the variable does not exist in sticky cache + * @param boolean $filter_result Filter for bad input if true + * + * @return mixed + * + * @todo should this filter the default value? + * @link http://docs.elgg.org/Tutorials/UI/StickyForms + * @since 1.8.0 + */ +function elgg_get_sticky_value($form_name, $variable = '', $default = NULL, $filter_result = true) { + if (isset($_SESSION['sticky_forms'][$form_name][$variable])) { + $value = $_SESSION['sticky_forms'][$form_name][$variable]; + if ($filter_result) { + // XSS filter result + $value = filter_tags($value); + } + return $value; + } + return $default; +} + +/** + * Get all the values in a sticky form in an array + * + * @param string $form_name The name of the form + * @param bool $filter_result Filter for bad input if true + * + * @return array + * @since 1.8.0 + */ +function elgg_get_sticky_values($form_name, $filter_result = true) { + if (!isset($_SESSION['sticky_forms'][$form_name])) { + return array(); + } + + $values = $_SESSION['sticky_forms'][$form_name]; + if ($filter_result) { + foreach ($values as $key => $value) { + // XSS filter result + $values[$key] = filter_tags($value); + } + } + return $values; +} + +/** + * Clear a specific sticky variable + * + * @param string $form_name The name of the form + * @param string $variable The name of the variable to clear + * + * @return void + * @link http://docs.elgg.org/Tutorials/UI/StickyForms + * @since 1.8.0 + */ +function elgg_clear_sticky_value($form_name, $variable) { + unset($_SESSION['sticky_forms'][$form_name][$variable]); +} + +/** * Page handler for autocomplete endpoint. * - * @param array $page Pages array + * @todo split this into functions/objects, this is way too big * - * @return unknown_type + * /livesearch?q=<query> + * + * Other options include: + * match_on string all or array(groups|users|friends) + * match_owner int 0/1 + * limit int default is 10 + * + * @param array $page + * @return string JSON string is returned and then exit + * @access private */ function input_livesearch_page_handler($page) { global $CONFIG; + // only return results to logged in users. - if (!$user = get_loggedin_user()) { + if (!$user = elgg_get_logged_in_user_entity()) { exit; } - if (!$q = get_input('q')) { + if (!$q = get_input('term', get_input('q'))) { exit; } @@ -121,91 +257,123 @@ function input_livesearch_page_handler($page) { $q = str_replace(array('_', '%'), array('\_', '\%'), $q); $match_on = get_input('match_on', 'all'); - if ($match_on == 'all' || $match_on[0] == 'all') { - $match_on = array('users', 'groups'); - } if (!is_array($match_on)) { $match_on = array($match_on); } + // all = users and groups + if (in_array('all', $match_on)) { + $match_on = array('users', 'groups'); + } + if (get_input('match_owner', false)) { - $owner_guid = $user->getGUID(); $owner_where = 'AND e.owner_guid = ' . $user->getGUID(); } else { - $owner_guid = null; $owner_where = ''; } - $limit = get_input('limit', 10); + $limit = sanitise_int(get_input('limit', 10)); // grab a list of entities and send them in json. $results = array(); - foreach ($match_on as $type) { - switch ($type) { - case 'all': - // only need to pull up title from objects. - - $options = array('owner_guid' => $owner_guid, 'limit' => $limit); - if (!$entities = elgg_get_entities($options) AND is_array($entities)) { - $results = array_merge($results, $entities); - } - break; - + foreach ($match_on as $match_type) { + switch ($match_type) { case 'users': $query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as ue, {$CONFIG->dbprefix}entities as e WHERE e.guid = ue.guid AND e.enabled = 'yes' AND ue.banned = 'no' - AND (ue.name LIKE '$q%' OR ue.username LIKE '$q%') + AND (ue.name LIKE '$q%' OR ue.name LIKE '% $q%' OR ue.username LIKE '$q%') LIMIT $limit "; if ($entities = get_data($query)) { foreach ($entities as $entity) { - $json = json_encode(array( + // @todo use elgg_get_entities (don't query in a loop!) + $entity = get_entity($entity->guid); + /* @var ElggUser $entity */ + if (!$entity) { + continue; + } + + if (in_array('groups', $match_on)) { + $value = $entity->guid; + } else { + $value = $entity->username; + } + + $output = elgg_view_list_item($entity, array( + 'use_hover' => false, + 'class' => 'elgg-autocomplete-item', + )); + + $icon = elgg_view_entity_icon($entity, 'tiny', array( + 'use_hover' => false, + )); + + $result = array( 'type' => 'user', 'name' => $entity->name, 'desc' => $entity->username, - 'icon' => '<img class="livesearch_icon" src="' . - get_entity($entity->guid)->getIcon('tiny') . '" />', - 'guid' => $entity->guid - )); - $results[$entity->name . rand(1, 100)] = $json; + 'guid' => $entity->guid, + 'label' => $output, + 'value' => $value, + 'icon' => $icon, + 'url' => $entity->getURL(), + ); + $results[$entity->name . rand(1, 100)] = $result; } } break; case 'groups': // don't return results if groups aren't enabled. - if (!is_plugin_enabled('groups')) { + if (!elgg_is_active_plugin('groups')) { continue; } $query = "SELECT * FROM {$CONFIG->dbprefix}groups_entity as ge, {$CONFIG->dbprefix}entities as e WHERE e.guid = ge.guid AND e.enabled = 'yes' $owner_where - AND (ge.name LIKE '$q%' OR ge.description LIKE '%$q%') + AND (ge.name LIKE '$q%' OR ge.name LIKE '% $q%' OR ge.description LIKE '% $q%') LIMIT $limit "; if ($entities = get_data($query)) { foreach ($entities as $entity) { - $json = json_encode(array( + // @todo use elgg_get_entities (don't query in a loop!) + $entity = get_entity($entity->guid); + /* @var ElggGroup $entity */ + if (!$entity) { + continue; + } + + $output = elgg_view_list_item($entity, array( + 'use_hover' => false, + 'class' => 'elgg-autocomplete-item', + )); + + $icon = elgg_view_entity_icon($entity, 'tiny', array( + 'use_hover' => false, + )); + + $result = array( 'type' => 'group', 'name' => $entity->name, 'desc' => strip_tags($entity->description), - 'icon' => '<img class="livesearch_icon" src="' - . get_entity($entity->guid)->getIcon('tiny') . '" />', - 'guid' => $entity->guid - )); - - $results[$entity->name . rand(1, 100)] = $json; + 'guid' => $entity->guid, + 'label' => $output, + 'value' => $entity->guid, + 'icon' => $icon, + 'url' => $entity->getURL(), + ); + + $results[$entity->name . rand(1, 100)] = $result; } } break; case 'friends': - $access = get_access_sql_suffix(); $query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as ue, {$CONFIG->dbprefix}entity_relationships as er, @@ -216,36 +384,54 @@ function input_livesearch_page_handler($page) { AND e.guid = ue.guid AND e.enabled = 'yes' AND ue.banned = 'no' - AND (ue.name LIKE '$q%' OR ue.username LIKE '$q%') + AND (ue.name LIKE '$q%' OR ue.name LIKE '% $q%' OR ue.username LIKE '$q%') LIMIT $limit "; if ($entities = get_data($query)) { foreach ($entities as $entity) { - $json = json_encode(array( + // @todo use elgg_get_entities (don't query in a loop!) + $entity = get_entity($entity->guid); + /* @var ElggUser $entity */ + if (!$entity) { + continue; + } + + $output = elgg_view_list_item($entity, array( + 'use_hover' => false, + 'class' => 'elgg-autocomplete-item', + )); + + $icon = elgg_view_entity_icon($entity, 'tiny', array( + 'use_hover' => false, + )); + + $result = array( 'type' => 'user', 'name' => $entity->name, 'desc' => $entity->username, - 'icon' => '<img class="livesearch_icon" src="' - . get_entity($entity->guid)->getIcon('tiny') . '" />', - 'guid' => $entity->guid - )); - $results[$entity->name . rand(1, 100)] = $json; + 'guid' => $entity->guid, + 'label' => $output, + 'value' => $entity->username, + 'icon' => $icon, + 'url' => $entity->getURL(), + ); + $results[$entity->name . rand(1, 100)] = $result; } } break; default: - // arbitrary subtype. - //@todo you cannot specify a subtype without a type. - // did this ever work? - elgg_get_entities(array('subtype' => $type, 'owner_guid' => $owner_guid)); + header("HTTP/1.0 400 Bad Request", true); + echo "livesearch: unknown match_on of $match_type"; + exit; break; } } ksort($results); - echo implode($results, "\n"); + header("Content-Type: application/json"); + echo json_encode(array_values($results)); exit; } @@ -253,10 +439,11 @@ function input_livesearch_page_handler($page) { * Register input functions and sanitize input * * @return void + * @access private */ function input_init() { // register an endpoint for live search / autocomplete. - register_page_handler('livesearch', 'input_livesearch_page_handler'); + elgg_register_page_handler('livesearch', 'input_livesearch_page_handler'); if (ini_get_bool('magic_quotes_gpc')) { @@ -330,4 +517,4 @@ function input_init() { } } -register_elgg_event_handler('init', 'system', 'input_init'); +elgg_register_event_handler('init', 'system', 'input_init'); diff --git a/engine/lib/install.php b/engine/lib/install.php deleted file mode 100644 index 47ad47e39..000000000 --- a/engine/lib/install.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -/** - * Elgg installation - * Various functions to assist with installing and upgrading the system - * - * @package Elgg.Core - * @subpackage Installation - */ - -// @todo - remove this internal function as soon as it is pulled from elgg_view() -function is_installed() { - global $CONFIG; - return $CONFIG->installed; -}
\ No newline at end of file diff --git a/engine/lib/languages.php b/engine/lib/languages.php index 3472d0d29..61ba91ddb 100644 --- a/engine/lib/languages.php +++ b/engine/lib/languages.php @@ -8,6 +8,65 @@ */ /** + * Given a message key, returns an appropriately translated full-text string + * + * @param string $message_key The short message code + * @param array $args An array of arguments to pass through vsprintf(). + * @param string $language Optionally, the standard language code + * (defaults to site/user default, then English) + * + * @return string Either the translated string, the English string, + * or the original language string. + */ +function elgg_echo($message_key, $args = array(), $language = "") { + global $CONFIG; + + static $CURRENT_LANGUAGE; + + // old param order is deprecated + if (!is_array($args)) { + elgg_deprecated_notice( + 'As of Elgg 1.8, the 2nd arg to elgg_echo() is an array of string replacements and the 3rd arg is the language.', + 1.8 + ); + + $language = $args; + $args = array(); + } + + if (!isset($CONFIG->translations)) { + // this means we probably had an exception before translations were initialized + register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); + } + + if (!$CURRENT_LANGUAGE) { + $CURRENT_LANGUAGE = get_language(); + } + if (!$language) { + $language = $CURRENT_LANGUAGE; + } + + if (isset($CONFIG->translations[$language][$message_key])) { + $string = $CONFIG->translations[$language][$message_key]; + } else if (isset($CONFIG->translations["en"][$message_key])) { + $string = $CONFIG->translations["en"][$message_key]; + $lang = $CONFIG->translations["en"][$language]; + elgg_log(sprintf('Missing %s translation for "%s" language key', $lang, $message_key), 'NOTICE'); + } else { + $string = $message_key; + elgg_log(sprintf('Missing English translation for "%s" language key', $message_key), 'NOTICE'); + } + + // only pass through if we have arguments to allow backward compatibility + // with manual sprintf() calls. + if ($args) { + $string = vsprintf($string, $args); + } + + return $string; +} + +/** * Add a translation. * * Translations are arrays in the Zend Translation array format, eg: @@ -18,7 +77,7 @@ * @param string $country_code Standard country code (eg 'en', 'nl', 'es') * @param array $language_array Formatted array of strings * - * @return true|false Depending on success + * @return bool Depending on success */ function add_translation($country_code, $language_array) { global $CONFIG; @@ -45,8 +104,6 @@ function add_translation($country_code, $language_array) { * @return string The language code for the site/user or "en" if not set */ function get_current_language() { - global $CONFIG; - $language = get_language(); if (!$language) { @@ -64,7 +121,7 @@ function get_current_language() { function get_language() { global $CONFIG; - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); $language = false; if (($user) && ($user->language)) { @@ -83,34 +140,37 @@ function get_language() { } /** - * Given a message shortcode, returns an appropriately translated full-text string - * - * @param string $message_key The short message code - * @param string $language Optionally, the standard language code - * (defaults to site/user default, then English) - * - * @return string Either the translated string or the original English string + * @access private */ -function elgg_echo($message_key, $language = "") { +function _elgg_load_translations() { global $CONFIG; - static $CURRENT_LANGUAGE; - if (!$CURRENT_LANGUAGE) { - $CURRENT_LANGUAGE = get_language(); - } - if (!$language) { - $language = $CURRENT_LANGUAGE; - } + if ($CONFIG->system_cache_enabled) { + $loaded = true; + $languages = array_unique(array('en', get_current_language())); + foreach ($languages as $language) { + $data = elgg_load_system_cache("$language.lang"); + if ($data) { + add_translation($language, unserialize($data)); + } else { + $loaded = false; + } + } - if (isset($CONFIG->translations[$language][$message_key])) { - return $CONFIG->translations[$language][$message_key]; - } else if (isset($CONFIG->translations["en"][$message_key])) { - return $CONFIG->translations["en"][$message_key]; + if ($loaded) { + $CONFIG->i18n_loaded_from_cache = true; + // this is here to force + $CONFIG->language_paths[dirname(dirname(dirname(__FILE__))) . "/languages/"] = true; + return; + } } - return $message_key; + // load core translations from languages directory + register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); } + + /** * When given a full path, finds translation files and loads them * @@ -118,11 +178,13 @@ function elgg_echo($message_key, $language = "") { * @param bool $load_all If true all languages are loaded, if * false only the current language + en are loaded * - * @return void + * @return bool success */ function register_translations($path, $load_all = false) { global $CONFIG; + $path = sanitise_filepath($path); + // Make a note of this path just incase we need to register this language later if (!isset($CONFIG->language_paths)) { $CONFIG->language_paths = array(); @@ -133,40 +195,72 @@ function register_translations($path, $load_all = false) { $current_language = get_current_language(); elgg_log("Translations loaded from: $path"); - if ($handle = opendir($path)) { - while ($language = readdir($handle)) { - if ( - ((in_array($language, array('en.php', $current_language . '.php'))) ) || - (($load_all) && (strpos($language, '.php') !== false)) - ) { - include_once($path . $language); + // only load these files unless $load_all is true. + $load_language_files = array( + 'en.php', + "$current_language.php" + ); + + $load_language_files = array_unique($load_language_files); + + $handle = opendir($path); + if (!$handle) { + elgg_log("Could not open language path: $path", 'ERROR'); + return false; + } + + $return = true; + while (false !== ($language = readdir($handle))) { + // ignore bad files + if (substr($language, 0, 1) == '.' || substr($language, -4) !== '.php') { + continue; + } + + if (in_array($language, $load_language_files) || $load_all) { + if (!include_once($path . $language)) { + $return = false; + continue; } } - } else { - elgg_log("Missing translation path $path", 'ERROR'); } + + return $return; } /** * Reload all translations from all registered paths. * - * This is only called by functions which need to know all possible translations, namely the - * statistic gathering ones. + * This is only called by functions which need to know all possible translations. * * @todo Better on demand loading based on language_paths array * - * @return bool + * @return void */ function reload_all_translations() { global $CONFIG; static $LANG_RELOAD_ALL_RUN; if ($LANG_RELOAD_ALL_RUN) { - return null; + return; } - foreach ($CONFIG->language_paths as $path => $dummy) { - register_translations($path, true); + if ($CONFIG->i18n_loaded_from_cache) { + $cache = elgg_get_system_cache(); + $cache_dir = $cache->getVariable("cache_path"); + $filenames = elgg_get_file_list($cache_dir, array(), array(), array(".lang")); + foreach ($filenames as $filename) { + if (preg_match('/([a-z]+)\.[^.]+$/', $filename, $matches)) { + $language = $matches[1]; + $data = elgg_load_system_cache("$language.lang"); + if ($data) { + add_translation($language, unserialize($data)); + } + } + } + } else { + foreach ($CONFIG->language_paths as $path => $dummy) { + register_translations($path, true); + } } $LANG_RELOAD_ALL_RUN = true; @@ -187,8 +281,8 @@ function get_installed_translations() { $installed = array(); foreach ($CONFIG->translations as $k => $v) { - $installed[$k] = elgg_echo($k, $k); - if (isadminloggedin()) { + $installed[$k] = elgg_echo($k, array(), $k); + if (elgg_is_admin_logged_in()) { $completeness = get_language_completeness($k); if (($completeness < 100) && ($k != 'en')) { $installed[$k] .= " (" . $completeness . "% " . elgg_echo('complete') . ")"; @@ -258,5 +352,3 @@ function get_missing_language_keys($language) { return false; } - -register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/");
\ No newline at end of file diff --git a/engine/lib/location.php b/engine/lib/location.php index f3aae709b..1534c7d7b 100644 --- a/engine/lib/location.php +++ b/engine/lib/location.php @@ -2,28 +2,24 @@ /** * Elgg geo-location tagging library. * - * @package Elgg - * @subpackage Core + * @package Elgg.Core + * @subpackage Location */ /** * Encode a location into a latitude and longitude, caching the result. * * Works by triggering the 'geocode' 'location' plugin - * hook, and requires a geocoding module to be installed - * activated in order to work. + * hook, and requires a geocoding plugin to be installed. * - * @param String $location The location, e.g. "London", or "24 Foobar Street, Gotham City" - * - * @return string + * @param string $location The location, e.g. "London", or "24 Foobar Street, Gotham City" + * @return string|false */ function elgg_geocode_location($location) { global $CONFIG; - // Handle cases where we are passed an array (shouldn't be - // but can happen if location is a tag field) if (is_array($location)) { - $location = implode(', ', $location); + return false; } $location = sanitise_string($location); @@ -38,7 +34,7 @@ function elgg_geocode_location($location) { // Trigger geocode event if not cached $return = false; - $return = trigger_plugin_hook('geocode', 'location', array('location' => $location), $return); + $return = elgg_trigger_plugin_hook('geocode', 'location', array('location' => $location), $return); // If returned, cache and return value if (($return) && (is_array($return))) { @@ -58,213 +54,104 @@ function elgg_geocode_location($location) { /** * Return entities within a given geographic area. * - * @param float $lat Latitude - * @param float $long Longitude - * @param float $radius The radius - * @param string $type The type of entity (eg "user", "object" etc) - * @param string $subtype The arbitrary subtype of the entity - * @param int $owner_guid The GUID of the owning user - * @param string $order_by The field to order by; by default, time_created desc - * @param int $limit The number of entities to return; 10 by default - * @param int $offset The indexing offset, 0 by default - * @param boolean $count Count entities - * @param int $site_guid Site GUID. 0 for current, -1 for any - * @param int|array $container_guid Container GUID + * Also accepts all options available to elgg_get_entities(). + * + * @see elgg_get_entities + * + * @param array $options Array in format: + * + * latitude => FLOAT Latitude of the location * - * @return array A list of entities. + * longitude => FLOAT Longitude of the location + * + * distance => FLOAT/ARR ( + * latitude => float, + * longitude => float, + * ) + * The distance in degrees that determines the search box. A + * single float will result in a square in degrees. + * @warning The Earth is round. + * + * @see ElggEntity::setLatLong() + * + * @return mixed If count, int. If not count, array. false on errors. + * @since 1.8.0 */ -function get_entities_in_area($lat, $long, $radius, $type = "", $subtype = "", $owner_guid = 0, -$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0, $container_guid) { +function elgg_get_entities_from_location(array $options = array()) { global $CONFIG; - - if ($subtype === false || $subtype === null || $subtype === 0) { + + if (!isset($options['latitude']) || !isset($options['longitude']) || + !isset($options['distance'])) { return false; } - $lat = (float)$lat; - $long = (float)$long; - $radius = (float)$radius; - - $order_by = sanitise_string($order_by); - $limit = (int)$limit; - $offset = (int)$offset; - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $where = array(); - - if (is_array($type)) { - $tempwhere = ""; - if (sizeof($type)) { - foreach ($type as $typekey => $subtypearray) { - foreach ($subtypearray as $subtypeval) { - $typekey = sanitise_string($typekey); - if (!empty($subtypeval)) { - $subtypeval = (int) get_subtype_id($typekey, $subtypeval); - } else { - $subtypeval = 0; - } - if (!empty($tempwhere)) { - $tempwhere .= " or "; - } - - $tempwhere .= "(e.type = '{$typekey}' and e.subtype = {$subtypeval})"; - } - } - } - if (!empty($tempwhere)) { - $where[] = "({$tempwhere})"; - } + if (!is_array($options['distance'])) { + $lat_distance = (float)$options['distance']; + $long_distance = (float)$options['distance']; } else { - $type = sanitise_string($type); - $subtype = get_subtype_id($type, $subtype); - - if ($type != "") { - $where[] = "e.type='$type'"; - } - - if ($subtype !== "") { - $where[] = "e.subtype=$subtype"; - } - } - - if ($owner_guid != "") { - if (!is_array($owner_guid)) { - $owner_array = array($owner_guid); - $owner_guid = (int) $owner_guid; - $where[] = "e.owner_guid = '$owner_guid'"; - } else if (sizeof($owner_guid) > 0) { - $owner_array = array_map('sanitise_int', $owner_guid); - // Cast every element to the owner_guid array to int - $owner_guid = implode(",", $owner_guid); // - $where[] = "e.owner_guid in ({$owner_guid})" ; // - } - if (is_null($container_guid)) { - $container_guid = $owner_array; - } + $lat_distance = (float)$options['distance']['latitude']; + $long_distance = (float)$options['distance']['longitude']; } - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; + $lat = (float)$options['latitude']; + $long = (float)$options['longitude']; + $lat_min = $lat - $lat_distance; + $lat_max = $lat + $lat_distance; + $long_min = $long - $long_distance; + $long_max = $long + $long_distance; + + $wheres = array(); + $wheres[] = "lat_name.string='geo:lat'"; + $wheres[] = "lat_value.string >= $lat_min"; + $wheres[] = "lat_value.string <= $lat_max"; + $wheres[] = "lon_name.string='geo:long'"; + $wheres[] = "lon_value.string >= $long_min"; + $wheres[] = "lon_value.string <= $long_max"; + + $joins = array(); + $joins[] = "JOIN {$CONFIG->dbprefix}metadata lat on e.guid=lat.entity_guid"; + $joins[] = "JOIN {$CONFIG->dbprefix}metastrings lat_name on lat.name_id=lat_name.id"; + $joins[] = "JOIN {$CONFIG->dbprefix}metastrings lat_value on lat.value_id=lat_value.id"; + $joins[] = "JOIN {$CONFIG->dbprefix}metadata lon on e.guid=lon.entity_guid"; + $joins[] = "JOIN {$CONFIG->dbprefix}metastrings lon_name on lon.name_id=lon_name.id"; + $joins[] = "JOIN {$CONFIG->dbprefix}metastrings lon_value on lon.value_id=lon_value.id"; + + // merge wheres to pass to get_entities() + if (isset($options['wheres']) && !is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } elseif (!isset($options['wheres'])) { + $options['wheres'] = array(); } + $options['wheres'] = array_merge($options['wheres'], $wheres); - if (!is_null($container_guid)) { - if (is_array($container_guid)) { - foreach ($container_guid as $key => $val) { - $container_guid[$key] = (int) $val; - } - $where[] = "e.container_guid in (" . implode(",", $container_guid) . ")"; - } else { - $container_guid = (int) $container_guid; - $where[] = "e.container_guid = {$container_guid}"; - } + // merge joins to pass to get_entities() + if (isset($options['joins']) && !is_array($options['joins'])) { + $options['joins'] = array($options['joins']); + } elseif (!isset($options['joins'])) { + $options['joins'] = array(); } + $options['joins'] = array_merge($options['joins'], $joins); - // Add the calendar stuff - $loc_join = " - JOIN {$CONFIG->dbprefix}metadata loc_start on e.guid=loc_start.entity_guid - JOIN {$CONFIG->dbprefix}metastrings loc_start_name on loc_start.name_id=loc_start_name.id - JOIN {$CONFIG->dbprefix}metastrings loc_start_value on loc_start.value_id=loc_start_value.id - - JOIN {$CONFIG->dbprefix}metadata loc_end on e.guid=loc_end.entity_guid - JOIN {$CONFIG->dbprefix}metastrings loc_end_name on loc_end.name_id=loc_end_name.id - JOIN {$CONFIG->dbprefix}metastrings loc_end_value on loc_end.value_id=loc_end_value.id - "; - - $lat_min = $lat - $radius; - $lat_max = $lat + $radius; - $long_min = $long - $radius; - $long_max = $long + $radius; - - $where[] = "loc_start_name.string='geo:lat'"; - $where[] = "loc_start_value.string>=$lat_min"; - $where[] = "loc_start_value.string<=$lat_max"; - $where[] = "loc_end_name.string='geo:long'"; - $where[] = "loc_end_value.string >= $long_min"; - $where[] = "loc_end_value.string <= $long_max"; - - if (!$count) { - $query = "SELECT e.* from {$CONFIG->dbprefix}entities e $loc_join where "; - } else { - $query = "SELECT count(e.guid) as total from {$CONFIG->dbprefix}entities e $loc_join where "; - } - foreach ($where as $w) { - $query .= " $w and "; - } - - $query .= get_access_sql_suffix('e'); // Add access controls - - if (!$count) { - $query .= " order by n.calendar_start $order_by"; - // Add order and limit - if ($limit) { - $query .= " limit $offset, $limit"; - } - $dt = get_data($query, "entity_row_to_elggstar"); - return $dt; - } else { - $total = get_data_row($query); - return $total->total; - } + return elgg_get_entities_from_relationship($options); } /** - * List entities in a given location + * Returns a viewable list of entities from location * - * @param string $location Location - * @param string $type The type of entity (eg "user", "object" etc) - * @param string $subtype The arbitrary subtype of the entity - * @param int $owner_guid The GUID of the owning user - * @param int $limit The number of entities to display per page (default: 10) - * @param bool $fullview Whether or not to display the full view (default: true) - * @param bool $viewtypetoggle Whether or not to allow gallery view - * @param bool $navigation Display pagination? Default: true + * @param array $options Options array * - * @return string A viewable list of entities - */ -function list_entities_location($location, $type= "", $subtype = "", $owner_guid = 0, $limit = 10, -$fullview = true, $viewtypetoggle = false, $navigation = true) { - - return list_entities_from_metadata('location', $location, $type, $subtype, $owner_guid, $limit, - $fullview, $viewtypetoggle, $navigation); -} - -/** - * List items within a given geographic area. - * - * @param real $lat Latitude - * @param real $long Longitude - * @param real $radius The radius - * @param string $type The type of entity (eg "user", "object" etc) - * @param string $subtype The arbitrary subtype of the entity - * @param int $owner_guid The GUID of the owning user - * @param int $limit The number of entities to display per page (default: 10) - * @param bool $fullview Whether or not to display the full view (default: true) - * @param bool $viewtypetoggle Whether or not to allow gallery view - * @param bool $navigation Display pagination? Default: true + * @see elgg_list_entities() + * @see elgg_get_entities_from_location() * - * @return string A viewable list of entities + * @return string The viewable list of entities + * @since 1.8.0 */ -function list_entities_in_area($lat, $long, $radius, $type= "", $subtype = "", $owner_guid = 0, -$limit = 10, $fullview = true, $viewtypetoggle = false, $navigation = true) { - - $offset = (int) get_input('offset'); - $count = get_entities_in_area($lat, $long, $radius, $type, $subtype, $owner_guid, - "", $limit, $offset, true); - $entities = get_entities_in_area($lat, $long, $radius, $type, $subtype, $owner_guid, - "", $limit, $offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, - $viewtypetoggle, $navigation); +function elgg_list_entities_from_location(array $options = array()) { + return elgg_list_entities($options, 'elgg_get_entities_from_location'); } // Some distances in degrees (approximate) +// @todo huh? see warning on elgg_get_entities_from_location() define("MILE", 0.01515); define("KILOMETER", 0.00932); - -// @todo get objects within x miles by entities, metadata and relationship - -// @todo List
\ No newline at end of file diff --git a/engine/lib/mb_wrapper.php b/engine/lib/mb_wrapper.php index da7a96c1f..68fa69005 100644 --- a/engine/lib/mb_wrapper.php +++ b/engine/lib/mb_wrapper.php @@ -11,7 +11,7 @@ if (is_callable('mb_internal_encoding')) { * NOTE: This differs from parse_str() by returning the results * instead of placing them in the local scope! * - * @param str $str The string + * @param string $str The string * * @return array * @since 1.7.0 @@ -230,4 +230,4 @@ function elgg_substr() { return call_user_func_array('mb_substr', $args); } return call_user_func_array('substr', $args); -}
\ No newline at end of file +} diff --git a/engine/lib/memcache.php b/engine/lib/memcache.php index 6e905b0d4..79b87e850 100644 --- a/engine/lib/memcache.php +++ b/engine/lib/memcache.php @@ -34,4 +34,24 @@ function is_memcache_available() { } return $memcache_available; +} + +/** + * Invalidate an entity in memcache + * + * @param int $entity_guid The GUID of the entity to invalidate + * + * @return void + * @access private + */ +function _elgg_invalidate_memcache_for_entity($entity_guid) { + static $newentity_cache; +
+ if ((!$newentity_cache) && (is_memcache_available())) {
+ $newentity_cache = new ElggMemcache('new_entity_cache');
+ } +
+ if ($newentity_cache) {
+ $newentity_cache->delete($entity_guid);
+ } }
\ No newline at end of file diff --git a/engine/lib/metadata.php b/engine/lib/metadata.php index 77267d8a2..fdb1b85f6 100644 --- a/engine/lib/metadata.php +++ b/engine/lib/metadata.php @@ -1,7 +1,7 @@ <?php /** * Elgg metadata - * Functions to manage object metadata. + * Functions to manage entity metadata. * * @package Elgg.Core * @subpackage DataModel.Metadata @@ -12,7 +12,8 @@ * * @param stdClass $row An object from the database * - * @return stdClass or ElggMetadata + * @return stdClass|ElggMetadata + * @access private */ function row_to_elggmetadata($row) { if (!($row instanceof stdClass)) { @@ -23,59 +24,30 @@ function row_to_elggmetadata($row) { } /** - * Get a specific item of metadata. + * Get a specific metadata object by its id. + * If you want multiple metadata objects, use + * {@link elgg_get_metadata()}. * - * @param int $id The item of metadata being retrieved. + * @param int $id The id of the metadata object being retrieved. * - * @return mixed + * @return ElggMetadata|false FALSE if not found */ -function get_metadata($id) { - global $CONFIG; - - $id = (int)$id; - $access = get_access_sql_suffix("e"); - $md_access = get_access_sql_suffix("m"); - - $query = "SELECT m.*, n.string as name, v.string as value from {$CONFIG->dbprefix}metadata m" - . " JOIN {$CONFIG->dbprefix}entities e on e.guid = m.entity_guid" - . " JOIN {$CONFIG->dbprefix}metastrings v on m.value_id = v.id" - . " JOIN {$CONFIG->dbprefix}metastrings n on m.name_id = n.id" - . " where m.id=$id and $access and $md_access"; - - - return row_to_elggmetadata(get_data_row($query)); +function elgg_get_metadata_from_id($id) { + return elgg_get_metastring_based_object_from_id($id, 'metadata'); } /** - * Removes metadata on an entity with a particular name, optionally with a given value. + * Deletes metadata using its ID. * - * @param int $entity_guid The entity GUID - * @param string $name The name of the metadata - * @param string $value The value of the item (useful to remove a single item of a set) - * - * @return bool Depending on success + * @param int $id The metadata ID to delete. + * @return bool */ -function remove_metadata($entity_guid, $name, $value = "") { - global $CONFIG; - $entity_guid = (int) $entity_guid; - $name = sanitise_string($name); - $value = sanitise_string($value); - - $query = "SELECT * from {$CONFIG->dbprefix}metadata" - . " WHERE entity_guid = $entity_guid and name_id=" . add_metastring($name); - - if ($value != "") { - $query .= " and value_id=" . add_metastring($value); - } - - if ($existing = get_data($query)) { - foreach ($existing as $ex) { - delete_metadata($ex->id); - } - return true; +function elgg_delete_metadata_by_id($id) { + $metadata = elgg_get_metadata_from_id($id); + if (!$metadata) { + return false; } - - return false; + return $metadata->delete(); } /** @@ -92,9 +64,9 @@ function remove_metadata($entity_guid, $name, $value = "") { * @param int $access_id Default is ACCESS_PRIVATE * @param bool $allow_multiple Allow multiple values for one key. Default is FALSE * - * @return int/bool id of metadata or FALSE if failure + * @return int|false id of metadata or FALSE if failure */ -function create_metadata($entity_guid, $name, $value, $value_type, $owner_guid, +function create_metadata($entity_guid, $name, $value, $value_type = '', $owner_guid = 0, $access_id = ACCESS_PRIVATE, $allow_multiple = false) { global $CONFIG; @@ -113,13 +85,11 @@ function create_metadata($entity_guid, $name, $value, $value_type, $owner_guid, } if ($owner_guid == 0) { - $owner_guid = get_loggedin_userid(); + $owner_guid = elgg_get_logged_in_user_guid(); } $access_id = (int)$access_id; - $id = false; - $query = "SELECT * from {$CONFIG->dbprefix}metadata" . " WHERE entity_guid = $entity_guid and name_id=" . add_metastring($name) . " limit 1"; @@ -134,37 +104,36 @@ function create_metadata($entity_guid, $name, $value, $value_type, $owner_guid, } else { // Support boolean types if (is_bool($value)) { - if ($value) { - $value = 1; - } else { - $value = 0; - } + $value = (int) $value; } // Add the metastrings - $value = add_metastring($value); - if (!$value) { + $value_id = add_metastring($value); + if (!$value_id) { return false; } - $name = add_metastring($name); - if (!$name) { + $name_id = add_metastring($name); + if (!$name_id) { return false; } // If ok then add it $query = "INSERT into {$CONFIG->dbprefix}metadata" . " (entity_guid, name_id, value_id, value_type, owner_guid, time_created, access_id)" - . " VALUES ($entity_guid, '$name','$value','$value_type', $owner_guid, $time, $access_id)"; + . " VALUES ($entity_guid, '$name_id','$value_id','$value_type', $owner_guid, $time, $access_id)"; $id = insert_data($query); if ($id !== false) { - $obj = get_metadata($id); - if (trigger_elgg_event('create', 'metadata', $obj)) { + $obj = elgg_get_metadata_from_id($id); + if (elgg_trigger_event('create', 'metadata', $obj)) { + + elgg_get_metadata_cache()->save($entity_guid, $name, $value, $allow_multiple); + return $id; } else { - delete_metadata($id); + elgg_delete_metadata_by_id($id); } } } @@ -173,9 +142,9 @@ function create_metadata($entity_guid, $name, $value, $value_type, $owner_guid, } /** - * Update an item of metadata. + * Update a specific piece of metadata. * - * @param int $id Metadata id + * @param int $id ID of the metadata to update * @param string $name Metadata name * @param string $value Metadata value * @param string $value_type Value type @@ -189,7 +158,7 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i $id = (int)$id; - if (!$md = get_metadata($id)) { + if (!$md = elgg_get_metadata_from_id($id)) { return false; } if (!$md->canEdit()) { @@ -203,6 +172,7 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i } if ($metabyname_memcache) { + // @todo fix memcache (name_id is not a property of ElggMetadata) $metabyname_memcache->delete("{$md->entity_guid}:{$md->name_id}"); } @@ -210,46 +180,42 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i $owner_guid = (int)$owner_guid; if ($owner_guid == 0) { - $owner_guid = get_loggedin_userid(); + $owner_guid = elgg_get_logged_in_user_guid(); } $access_id = (int)$access_id; - $access = get_access_sql_suffix(); - // Support boolean types (as integers) if (is_bool($value)) { - if ($value) { - $value = 1; - } else { - $value = 0; - } + $value = (int) $value; } // Add the metastring - $value = add_metastring($value); - if (!$value) { + $value_id = add_metastring($value); + if (!$value_id) { return false; } - $name = add_metastring($name); - if (!$name) { + $name_id = add_metastring($name); + if (!$name_id) { return false; } // If ok then add it $query = "UPDATE {$CONFIG->dbprefix}metadata" - . " set value_id='$value', value_type='$value_type', access_id=$access_id," - . " owner_guid=$owner_guid where id=$id and name_id='$name'"; + . " set name_id='$name_id', value_id='$value_id', value_type='$value_type', access_id=$access_id," + . " owner_guid=$owner_guid where id=$id"; $result = update_data($query); if ($result !== false) { - $obj = get_metadata($id); - if (trigger_elgg_event('update', 'metadata', $obj)) { - return true; - } else { - delete_metadata($id); - } + + elgg_get_metadata_cache()->save($md->entity_guid, $name, $value); + + // @todo this event tells you the metadata has been updated, but does not + // let you do anything about it. What is needed is a plugin hook before + // the update that passes old and new values. + $obj = elgg_get_metadata_from_id($id); + elgg_trigger_event('update', 'metadata', $obj); } return $result; @@ -263,7 +229,7 @@ function update_metadata($id, $name, $value, $value_type, $owner_guid, $access_i * associative arrays and there is no guarantee on the ordering in the array. * * @param int $entity_guid The entity to attach the metadata to - * @param string $name_and_values Associative array - a value can be a string, number, bool + * @param array $name_and_values Associative array - a value can be a string, number, bool * @param string $value_type 'text', 'integer', or '' for automatic detection * @param int $owner_guid GUID of entity that owns the metadata * @param int $access_id Default is ACCESS_PRIVATE @@ -285,222 +251,148 @@ $access_id = ACCESS_PRIVATE, $allow_multiple = false) { } /** - * Delete an item of metadata, where the current user has access. + * Returns metadata. Accepts all elgg_get_entities() options for entity + * restraints. * - * @param int $id The item of metadata to delete. + * @see elgg_get_entities * - * @return bool + * @warning 1.7's find_metadata() didn't support limits and returned all metadata. + * This function defaults to a limit of 25. There is probably not a reason + * for you to return all metadata unless you're exporting an entity, + * have other restraints in place, or are doing something horribly + * wrong in your code. + * + * @param array $options Array in format: + * + * metadata_names => NULL|ARR metadata names + * metadata_values => NULL|ARR metadata values + * metadata_ids => NULL|ARR metadata ids + * metadata_case_sensitive => BOOL Overall Case sensitive + * metadata_owner_guids => NULL|ARR guids for metadata owners + * metadata_created_time_lower => INT Lower limit for created time. + * metadata_created_time_upper => INT Upper limit for created time. + * metadata_calculation => STR Perform the MySQL function on the metadata values returned. + * The "metadata_calculation" option causes this function to + * return the result of performing a mathematical calculation on + * all metadata that match the query instead of returning + * ElggMetadata objects. + * + * @return ElggMetadata[]|mixed + * @since 1.8.0 */ -function delete_metadata($id) { - global $CONFIG; - - $id = (int)$id; - $metadata = get_metadata($id); +function elgg_get_metadata(array $options = array()) { - if ($metadata) { - // Tidy up if memcache is enabled. - static $metabyname_memcache; - if ((!$metabyname_memcache) && (is_memcache_available())) { - $metabyname_memcache = new ElggMemcache('metabyname_memcache'); - } - - if ($metabyname_memcache) { - $metabyname_memcache->delete("{$metadata->entity_guid}:{$metadata->name_id}"); - } - - if (($metadata->canEdit()) && (trigger_elgg_event('delete', 'metadata', $metadata))) { - return delete_data("DELETE from {$CONFIG->dbprefix}metadata where id=$id"); - } + // @todo remove support for count shortcut - see #4393 + // support shortcut of 'count' => true for 'metadata_calculation' => 'count' + if (isset($options['count']) && $options['count']) { + $options['metadata_calculation'] = 'count'; + unset($options['count']); } - return false; + $options['metastring_type'] = 'metadata'; + return elgg_get_metastring_based_objects($options); } /** - * Return the metadata values that match your query. + * Deletes metadata based on $options. * - * @param int $entity_guid Entity GUID - * @param string $meta_name Metadata name + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! + * This requires at least one constraint: metadata_owner_guid(s), + * metadata_name(s), metadata_value(s), or guid(s) must be set. * - * @return mixed either a value, an array of ElggMetadata or false. + * @param array $options An options array. {@see elgg_get_metadata()} + * @return bool|null true on success, false on failure, null if no metadata to delete. + * @since 1.8.0 */ -function get_metadata_byname($entity_guid, $meta_name) { - global $CONFIG; - - $meta_name = get_metastring_id($meta_name); - - if (empty($meta_name)) { +function elgg_delete_metadata(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'metadata')) { return false; } + $options['metastring_type'] = 'metadata'; + $result = elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); - $entity_guid = (int)$entity_guid; - $access = get_access_sql_suffix("e"); - $md_access = get_access_sql_suffix("m"); - - // If memcache is available then cache this (cache only by name for now - // since this is the most common query) - $meta = null; - static $metabyname_memcache; - if ((!$metabyname_memcache) && (is_memcache_available())) { - $metabyname_memcache = new ElggMemcache('metabyname_memcache'); - } - if ($metabyname_memcache) { - $meta = $metabyname_memcache->load("{$entity_guid}:{$meta_name}"); - } - if ($meta) { - return $meta; - } - - $query = "SELECT m.*, n.string as name, v.string as value" - . " from {$CONFIG->dbprefix}metadata m" - . " JOIN {$CONFIG->dbprefix}entities e ON e.guid = m.entity_guid" - . " JOIN {$CONFIG->dbprefix}metastrings v on m.value_id = v.id" - . " JOIN {$CONFIG->dbprefix}metastrings n on m.name_id = n.id" - . " where m.entity_guid=$entity_guid and m.name_id='$meta_name'" - . " and $access and $md_access ORDER BY m.id ASC" ; - - $result = get_data($query, "row_to_elggmetadata"); - - if (!$result) { - return false; - } - - // Cache if memcache available - if ($metabyname_memcache) { - if (count($result) == 1) { - $r = $result[0]; - } else { - $r = $result; - } - // This is a bit of a hack - we shorten the expiry on object - // metadata so that it'll be gone in an hour. This means that - // deletions and more importantly updates will filter through eventually. - $metabyname_memcache->setDefaultExpiry(3600); - $metabyname_memcache->save("{$entity_guid}:{$meta_name}", $r); - } - if (count($result) == 1) { - return $result[0]; - } + // This moved last in case an object's constructor sets metadata. Currently the batch + // delete process has to create the entity to delete its metadata. See #5214 + elgg_get_metadata_cache()->invalidateByOptions('delete', $options); return $result; } /** - * Return all the metadata for a given GUID. + * Disables metadata based on $options. * - * @param int $entity_guid Entity GUID + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! * - * @return mixed + * @param array $options An options array. {@See elgg_get_metadata()} + * @return bool|null true on success, false on failure, null if no metadata disabled. + * @since 1.8.0 */ -function get_metadata_for_entity($entity_guid) { - global $CONFIG; - - $entity_guid = (int)$entity_guid; - $access = get_access_sql_suffix("e"); - $md_access = get_access_sql_suffix("m"); +function elgg_disable_metadata(array $options) { + if (!elgg_is_valid_options_for_batch_operation($options, 'metadata')) { + return false; + } - $query = "SELECT m.*, n.string as name, v.string as value - from {$CONFIG->dbprefix}metadata m - JOIN {$CONFIG->dbprefix}entities e ON e.guid = m.entity_guid - JOIN {$CONFIG->dbprefix}metastrings v on m.value_id = v.id - JOIN {$CONFIG->dbprefix}metastrings n on m.name_id = n.id - where m.entity_guid=$entity_guid and $access and $md_access"; + elgg_get_metadata_cache()->invalidateByOptions('disable', $options); + + // if we can see hidden (disabled) we need to use the offset + // otherwise we risk an infinite loop if there are more than 50 + $inc_offset = access_get_show_hidden_status(); - return get_data($query, "row_to_elggmetadata"); + $options['metastring_type'] = 'metadata'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset); } /** - * Get the metadata where the entities they are referring to match a given criteria. + * Enables metadata based on $options. * - * @param mixed $meta_name Metadata name - * @param mixed $meta_value Metadata value - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $limit Limit - * @param int $offset Offset - * @param string $order_by Optional ordering. - * @param int $site_guid Site GUID. 0 for current, -1 for any + * @warning Unlike elgg_get_metadata() this will not accept an empty options array! * - * @return mixed + * @warning In order to enable metadata, you must first use + * {@link access_show_hidden_entities()}. + * + * @param array $options An options array. {@See elgg_get_metadata()} + * @return bool|null true on success, false on failure, null if no metadata enabled. + * @since 1.8.0 */ -function find_metadata($meta_name = "", $meta_value = "", $entity_type = "", $entity_subtype = "", - $limit = 10, $offset = 0, $order_by = "", $site_guid = 0) { - global $CONFIG; - - $meta_n = get_metastring_id($meta_name); - $meta_v = get_metastring_id($meta_value); - - $entity_type = sanitise_string($entity_type); - $entity_subtype = get_subtype_id($entity_type, $entity_subtype); - $limit = (int)$limit; - $offset = (int)$offset; - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - - $order_by = sanitise_string($order_by); - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $where = array(); - - if ($entity_type != "") { - $where[] = "e.type='$entity_type'"; - } - - if ($entity_subtype) { - $where[] = "e.subtype=$entity_subtype"; - } - - if ($meta_name != "") { - if (!$meta_v) { - // The value is set, but we didn't get a value... so something went wrong. - return false; - } - $where[] = "m.name_id='$meta_n'"; - } - if ($meta_value != "") { - // The value is set, but we didn't get a value... so something went wrong. - if (!$meta_v) { - return false; - } - $where[] = "m.value_id='$meta_v'"; - } - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; +function elgg_enable_metadata(array $options) { + if (!$options || !is_array($options)) { + return false; } - $query = "SELECT m.*, n.string as name, v.string as value from {$CONFIG->dbprefix}entities e - JOIN {$CONFIG->dbprefix}metadata m on e.guid = m.entity_guid - JOIN {$CONFIG->dbprefix}metastrings v on m.value_id = v.id - JOIN {$CONFIG->dbprefix}metastrings n on m.name_id = n.id where"; + elgg_get_metadata_cache()->invalidateByOptions('enable', $options); - foreach ($where as $w) { - $query .= " $w and "; - } - $query .= get_access_sql_suffix("e"); // Add access controls - $query .= ' and ' . get_access_sql_suffix("m"); // Add access controls - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - - return get_data($query, "row_to_elggmetadata"); + $options['metastring_type'] = 'metadata'; + return elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback'); } /** + * ElggEntities interfaces + */ + +/** * Returns entities based upon metadata. Also accepts all * options available to elgg_get_entities(). Supports * the singular option shortcut. * - * NB: Using metadata_names and metadata_values results in a + * @note Using metadata_names and metadata_values results in a * "names IN (...) AND values IN (...)" clause. This is subtly * differently than default multiple metadata_name_value_pairs, which use * "(name = value) AND (name = value)" clauses. * * When in doubt, use name_value_pairs. * + * To ask for entities that do not have a metadata value, use a custom + * where clause like this: + * + * $options['wheres'][] = "NOT EXISTS ( + * SELECT 1 FROM {$dbprefix}metadata md + * WHERE md.entity_guid = e.guid + * AND md.name_id = $name_metastring_id + * AND md.value_id = $value_metastring_id)"; + * + * Note the metadata name and value has been denormalized in the above example. + * * @see elgg_get_entities - * @see elgg_get_entities_from_annotations * * @param array $options Array in format: * @@ -514,9 +406,11 @@ function find_metadata($meta_name = "", $meta_value = "", $entity_type = "", $en * 'operand' => '=', * 'case_sensitive' => TRUE * ) - * Currently if multiple values are sent via + * Currently if multiple values are sent via * an array (value => array('value1', 'value2') * the pair's operand will be forced to "IN". + * If passing "IN" as the operand and a string as the value, + * the value must be a properly quoted and escaped string. * * metadata_name_value_pairs_operator => NULL|STR The operator to use for combining * (name = value) OPERATOR (name = value); default AND @@ -532,20 +426,20 @@ function find_metadata($meta_name = "", $meta_value = "", $entity_type = "", $en * * metadata_owner_guids => NULL|ARR guids for metadata owners * - * @return array + * @return ElggEntity[]|mixed If count, int. If not count, array. false on errors. * @since 1.7.0 */ function elgg_get_entities_from_metadata(array $options = array()) { $defaults = array( - 'metadata_names' => ELGG_ENTITIES_ANY_VALUE, - 'metadata_values' => ELGG_ENTITIES_ANY_VALUE, - 'metadata_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, + 'metadata_names' => ELGG_ENTITIES_ANY_VALUE, + 'metadata_values' => ELGG_ENTITIES_ANY_VALUE, + 'metadata_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, - 'metadata_name_value_pairs_operator'=> 'AND', - 'metadata_case_sensitive' => TRUE, - 'order_by_metadata' => array(), + 'metadata_name_value_pairs_operator' => 'AND', + 'metadata_case_sensitive' => TRUE, + 'order_by_metadata' => array(), - 'metadata_owner_guids' => ELGG_ENTITIES_ANY_VALUE, + 'metadata_owner_guids' => ELGG_ENTITIES_ANY_VALUE, ); $options = array_merge($defaults, $options); @@ -563,66 +457,6 @@ function elgg_get_entities_from_metadata(array $options = array()) { } /** - * Returns options to pass to elgg_get_entities() for metastrings operations. - * - * @param string $type Metastring type: annotations or metadata - * @param array $options Options - * - * @return array - * @since 1.7.0 - */ -function elgg_entities_get_metastrings_options($type, $options) { - $valid_types = array('metadata', 'annotation'); - if (!in_array($type, $valid_types)) { - return FALSE; - } - - // the options for annotations are singular (annotation_name) but the table - // is plural (elgg_annotations) so rewrite for the table name. - $n_table = ($type == 'annotation') ? 'annotations' : $type; - - $singulars = array("{$type}_name", "{$type}_value", - "{$type}_name_value_pair", "{$type}_owner_guid"); - $options = elgg_normalise_plural_options_array($options, $singulars); - - $clauses = elgg_get_entity_metadata_where_sql('e', $n_table, $options["{$type}_names"], - $options["{$type}_values"], $options["{$type}_name_value_pairs"], - $options["{$type}_name_value_pairs_operator"], $options["{$type}_case_sensitive"], - $options["order_by_{$type}"], $options["{$type}_owner_guids"]); - - if ($clauses) { - // merge wheres to pass to get_entities() - if (isset($options['wheres']) && !is_array($options['wheres'])) { - $options['wheres'] = array($options['wheres']); - } elseif (!isset($options['wheres'])) { - $options['wheres'] = array(); - } - - $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']); - - // merge joins to pass to get_entities() - if (isset($options['joins']) && !is_array($options['joins'])) { - $options['joins'] = array($options['joins']); - } elseif (!isset($options['joins'])) { - $options['joins'] = array(); - } - - $options['joins'] = array_merge($options['joins'], $clauses['joins']); - - if ($clauses['orders']) { - $order_by_metadata = implode(", ", $clauses['orders']); - if (isset($options['order_by']) && $options['order_by']) { - $options['order_by'] = "$order_by_metadata, {$options['order_by']}"; - } else { - $options['order_by'] = "$order_by_metadata, e.time_created DESC"; - } - } - } - - return $options; -} - -/** * Returns metadata name and value SQL where for entities. * NB: $names and $values are not paired. Use $pairs for this. * Pairs default to '=' operand. @@ -630,19 +464,20 @@ function elgg_entities_get_metastrings_options($type, $options) { * This function is reused for annotations because the tables are * exactly the same. * - * @param string $e_table Entities table name - * @param string $n_table Normalized metastrings table name (Where entities, + * @param string $e_table Entities table name + * @param string $n_table Normalized metastrings table name (Where entities, * values, and names are joined. annotations / metadata) - * @param arr|null $names Array of names - * @param arr|null $values Array of values - * @param arr|null $pairs Array of names / values / operands - * @param and|or $pair_operator Operator to use to join the where clauses for pairs - * @param bool $case_sensitive Case sensitive metadata names? - * @param arr|null $order_by_metadata Array of names / direction - * @param arr|null $owner_guids Array of owner GUIDs - * - * @return FALSE|array False on fail, array('joins', 'wheres') + * @param array|null $names Array of names + * @param array|null $values Array of values + * @param array|null $pairs Array of names / values / operands + * @param string $pair_operator ("AND" or "OR") Operator to use to join the where clauses for pairs + * @param bool $case_sensitive Case sensitive metadata names? + * @param array|null $order_by_metadata Array of names / direction + * @param array|null $owner_guids Array of owner GUIDs + * + * @return false|array False on fail, array('joins', 'wheres') * @since 1.7.0 + * @access private */ function elgg_get_entity_metadata_where_sql($e_table, $n_table, $names = NULL, $values = NULL, $pairs = NULL, $pair_operator = 'AND', $case_sensitive = TRUE, $order_by_metadata = NULL, @@ -787,6 +622,8 @@ $owner_guids = NULL) { // if the operand is IN don't quote it because quoting should be done already. if (is_numeric($pair['value'])) { $value = sanitise_string($pair['value']); + } else if (is_bool($pair['value'])) { + $value = (int) $pair['value']; } else if (is_array($pair['value'])) { $values_array = array(); @@ -827,7 +664,7 @@ $owner_guids = NULL) { $i++; } - if ($where = implode (" $pair_operator ", $pair_wheres)) { + if ($where = implode(" $pair_operator ", $pair_wheres)) { $wheres[] = "($where)"; } } @@ -849,7 +686,7 @@ $owner_guids = NULL) { } if (is_array($order_by_metadata)) { - if ((count($order_by_metadata) > 0) && !is_array($order_by_metadata[0])) { + if ((count($order_by_metadata) > 0) && !isset($order_by_metadata[0])) { // singleton, so fix $order_by_metadata = array($order_by_metadata); } @@ -885,132 +722,6 @@ $owner_guids = NULL) { } /** - * Return a list of entities based on the given search criteria. - * - * @deprecated 1.7 use elgg_get_entities_from_metadata(). - * - * @param mixed $meta_name Metadat name - * @param mixed $meta_value Metadata value - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $owner_guid Owner GUID - * @param int $limit Limit - * @param int $offset Offset - * @param string $order_by Optional ordering. - * @param int $site_guid Site GUID. 0 for current, -1 for any. - * @param bool $count Return a count instead of entities - * @param bool $case_sensitive Metadata names case sensitivity - * - * @return int|array A list of entities, or a count if $count is set to true - */ -function get_entities_from_metadata($meta_name, $meta_value = "", $entity_type = "", -$entity_subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", -$site_guid = 0, $count = FALSE, $case_sensitive = TRUE) { - - elgg_deprecated_notice('get_entities_from_metadata() was deprecated by elgg_get_entities_from_metadata()!', 1.7); - - $options = array(); - - $options['metadata_names'] = $meta_name; - - if ($meta_value) { - $options['metadata_values'] = $meta_value; - } - - if ($entity_type) { - $options['types'] = $entity_type; - } - - if ($entity_subtype) { - $options['subtypes'] = $entity_subtype; - } - - if ($owner_guid) { - if (is_array($owner_guid)) { - $options['owner_guids'] = $owner_guid; - } else { - $options['owner_guid'] = $owner_guid; - } - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } - - if ($order_by) { - $options['order_by']; - } - - if ($site_guid) { - $options['site_guid']; - } - - if ($count) { - $options['count'] = $count; - } - - // need to be able to pass false - $options['metadata_case_sensitive'] = $case_sensitive; - - return elgg_get_entities_from_metadata($options); -} - -/** - * Return a list of entities suitable for display based on the given search criteria. - * - * @see elgg_view_entity_list - * - * @deprecated 1.8 Use elgg_list_entities_from_metadata - * - * @param mixed $meta_name Metadata name to search on - * @param mixed $meta_value The value to match, optionally - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity - * @param int $owner_guid Owner GUID - * @param int $limit Number of entities to display per page - * @param bool $fullview WDisplay the full view (default: true) - * @param bool $viewtypetoggle Allow users to toggle to the gallery view. Default: true - * @param bool $pagination Display pagination? Default: true - * @param bool $case_sensitive Case sensitive metadata names? - * - * @return string - * - * @return string A list of entities suitable for display - */ -function list_entities_from_metadata($meta_name, $meta_value = "", -$entity_type = ELGG_ENTITIES_ANY_VALUE, $entity_subtype = ELGG_ENTITIES_ANY_VALUE, -$owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = true, -$pagination = true, $case_sensitive = true) { - - elgg_deprecated_notice('list_entities_from_metadata() was deprecated by elgg_list_entities_from_metadata()!', 1.8); - - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $options = array( - 'metadata_name' => $meta_name, - 'metadata_value' => $meta_value, - 'types' => $entity_type, - 'subtypes' => $entity_subtype, - 'owner_guid' => $owner_guid, - 'limit' => $limit, - 'offset' => $offset, - 'count' => TRUE, - 'metadata_case_sensitive' => $case_sensitive - ); - $count = elgg_get_entities_from_metadata($options); - - $options['count'] = FALSE; - $entities = elgg_get_entities_from_metadata($options); - - return elgg_view_entity_list($entities, $count, $offset, $limit, - $fullview, $viewtypetoggle, $pagination); -} - -/** * Returns a list of entities filtered by provided metadata. * * @see elgg_get_entities_from_metadata @@ -1025,153 +736,8 @@ function elgg_list_entities_from_metadata($options) { } /** - * Return entities from metadata - * - * @deprecated 1.7. Use elgg_get_entities_from_metadata(). - * - * @param mixed $meta_array Metadata name - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $owner_guid Owner GUID - * @param int $limit Limit - * @param int $offset Offset - * @param string $order_by Optional ordering. - * @param int $site_guid Site GUID. 0 for current, -1 for any. - * @param bool $count Return a count instead of entities - * @param bool $meta_array_operator Operator for metadata values - * - * @return int|array A list of entities, or a count if $count is set to true + * Other functions */ -function get_entities_from_metadata_multi($meta_array, $entity_type = "", $entity_subtype = "", -$owner_guid = 0, $limit = 10, $offset = 0, $order_by = "", $site_guid = 0, -$count = false, $meta_array_operator = 'and') { - - elgg_deprecated_notice('get_entities_from_metadata_multi() was deprecated by elgg_get_entities_from_metadata()!', 1.7); - - if (!is_array($meta_array) || sizeof($meta_array) == 0) { - return false; - } - - $options = array(); - - $options['metadata_name_value_pairs'] = $meta_array; - - if ($entity_type) { - $options['types'] = $entity_type; - } - - if ($entity_subtype) { - $options['subtypes'] = $entity_subtype; - } - - if ($owner_guid) { - if (is_array($owner_guid)) { - $options['owner_guids'] = $owner_guid; - } else { - $options['owner_guid'] = $owner_guid; - } - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } - - if ($order_by) { - $options['order_by']; - } - - if ($site_guid) { - $options['site_guid']; - } - - if ($count) { - $options['count'] = $count; - } - - $options['metadata_name_value_pairs_operator'] = $meta_array_operator; - - return elgg_get_entities_from_metadata($options); -} - -/** - * Returns a viewable list of entities based on the given search criteria. - * - * @see elgg_view_entity_list - * - * @param array $meta_array Array of 'name' => 'value' pairs - * @param string $entity_type The type of entity to look for, eg 'site' or 'object' - * @param string $entity_subtype The subtype of the entity. - * @param int $owner_guid Owner GUID - * @param int $limit Limit - * @param bool $fullview WDisplay the full view (default: true) - * @param bool $viewtypetoggle Allow users to toggle to the gallery view. Default: true - * @param bool $pagination Display pagination? Default: true - * - * @return string List of ElggEntities suitable for display - */ -function list_entities_from_metadata_multi($meta_array, $entity_type = "", $entity_subtype = "", -$owner_guid = 0, $limit = 10, $fullview = true, $viewtypetoggle = true, $pagination = true) { - - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = get_entities_from_metadata_multi($meta_array, $entity_type, $entity_subtype, - $owner_guid, $limit, $offset, "", $site_guid, true); - $entities = get_entities_from_metadata_multi($meta_array, $entity_type, $entity_subtype, - $owner_guid, $limit, $offset, "", $site_guid, false); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, - $viewtypetoggle, $pagination); -} - -/** - * Clear all the metadata for a given entity, assuming you have access to that metadata. - * - * @param int $entity_guid Entity GUID - * - * @return bool - */ -function clear_metadata($entity_guid) { - global $CONFIG; - - $entity_guid = (int)$entity_guid; - if ($entity = get_entity($entity_guid)) { - if ($entity->canEdit()) { - return delete_data("DELETE from {$CONFIG->dbprefix}metadata where entity_guid={$entity_guid}"); - } - } - return false; -} - -/** - * Clear all annotations belonging to a given owner_guid - * - * @param int $owner_guid The owner - * - * @return bool - */ -function clear_metadata_by_owner($owner_guid) { - global $CONFIG; - - $owner_guid = (int)$owner_guid; - - $metas = get_data("SELECT id from {$CONFIG->dbprefix}metadata WHERE owner_guid=$owner_guid"); - $deleted = 0; - - if (is_array($metas)) { - foreach ($metas as $id) { - // Is this the best way? - if (delete_metadata($id->id)) { - $deleted++; - } - } - } - - return $deleted; -} /** * Handler called by trigger_plugin_hook on the "export" event. @@ -1182,6 +748,9 @@ function clear_metadata_by_owner($owner_guid) { * @param mixed $params Params * * @return array + * @access private + * + * @throws InvalidParameterException */ function export_metadata_plugin_hook($hook, $entity_type, $returnvalue, $params) { // Sanity check values @@ -1193,12 +762,13 @@ function export_metadata_plugin_hook($hook, $entity_type, $returnvalue, $params) throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonArrayReturnValue')); } - $guid = (int)$params['guid']; - $name = $params['name']; - - $result = get_metadata_for_entity($guid); + $result = elgg_get_metadata(array( + 'guid' => (int)$params['guid'], + 'limit' => 0, + )); if ($result) { + /* @var ElggMetadata[] $result */ foreach ($result as $r) { $returnvalue[] = $r->export(); } @@ -1209,7 +779,7 @@ function export_metadata_plugin_hook($hook, $entity_type, $returnvalue, $params) /** * Takes in a comma-separated string and returns an array of tags - * which have been trimmed and set to lower case + * which have been trimmed * * @param string $string Comma-separated tag string * @@ -1218,17 +788,12 @@ function export_metadata_plugin_hook($hook, $entity_type, $returnvalue, $params) function string_to_tag_array($string) { if (is_string($string)) { $ar = explode(",", $string); - // trim blank spaces $ar = array_map('trim', $ar); - // make lower case : [Marcus Povey 20090605 - Using mb wrapper function - // using UTF8 safe function where available] - $ar = array_map('elgg_strtolower', $ar); - // Remove null values $ar = array_filter($ar, 'is_not_null'); + $ar = array_map('strip_tags', $ar); return $ar; } return false; - } /** @@ -1252,8 +817,9 @@ function metadata_array_to_values($array) { } /** - * Get the URL for this item of metadata, by default this - * links to the export handler in the current view. + * Get the URL for this metadata + * + * By default this links to the export handler in the current view. * * @param int $id Metadata ID * @@ -1262,7 +828,7 @@ function metadata_array_to_values($array) { function get_metadata_url($id) { $id = (int)$id; - if ($extender = get_metadata($id)) { + if ($extender = elgg_get_metadata_from_id($id)) { return get_extender_url($extender); } return false; @@ -1318,10 +884,10 @@ function is_metadata_independent($type, $subtype) { function metadata_update($event, $object_type, $object) { if ($object instanceof ElggEntity) { if (!is_metadata_independent($object->getType(), $object->getSubtype())) { - global $CONFIG; + $db_prefix = elgg_get_config('dbprefix'); $access_id = (int) $object->access_id; $guid = (int) $object->getGUID(); - $query = "update {$CONFIG->dbprefix}metadata set access_id = {$access_id} where entity_guid = {$guid}"; + $query = "update {$db_prefix}metadata set access_id = {$access_id} where entity_guid = {$guid}"; update_data($query); } } @@ -1331,23 +897,67 @@ function metadata_update($event, $object_type, $object) { /** * Register a metadata url handler. * - * @param string $function_name The function. * @param string $extender_name The name, default 'all'. + * @param string $function The function name. * * @return bool */ -function register_metadata_url_handler($function_name, $extender_name = "all") { - return register_extender_url_handler($function_name, 'metadata', $extender_name); +function elgg_register_metadata_url_handler($extender_name, $function) { + return elgg_register_extender_url_handler('metadata', $extender_name, $function); +} + +/** + * Get the global metadata cache instance + * + * @return ElggVolatileMetadataCache + * + * @access private + */ +function elgg_get_metadata_cache() { + global $CONFIG; + if (empty($CONFIG->local_metadata_cache)) { + $CONFIG->local_metadata_cache = new ElggVolatileMetadataCache(); + } + return $CONFIG->local_metadata_cache; +} + +/** + * Invalidate the metadata cache based on options passed to various *_metadata functions + * + * @param string $action Action performed on metadata. "delete", "disable", or "enable" + * @param array $options Options passed to elgg_(delete|disable|enable)_metadata + * @return void + */ +function elgg_invalidate_metadata_cache($action, array $options) { + // remove as little as possible, optimizing for common cases + $cache = elgg_get_metadata_cache(); + if (empty($options['guid'])) { + // safest to clear everything unless we want to make this even more complex :( + $cache->flush(); + } else { + if (empty($options['metadata_name'])) { + // safest to clear the whole entity + $cache->clear($options['guid']); + } else { + switch ($action) { + case 'delete': + $cache->markEmpty($options['guid'], $options['metadata_name']); + break; + default: + $cache->markUnknown($options['guid'], $options['metadata_name']); + } + } + } } /** Register the hook */ -register_plugin_hook("export", "all", "export_metadata_plugin_hook", 2); +elgg_register_plugin_hook_handler("export", "all", "export_metadata_plugin_hook", 2); /** Call a function whenever an entity is updated **/ -register_elgg_event_handler('update', 'all', 'metadata_update'); +elgg_register_event_handler('update', 'all', 'metadata_update'); // unit testing -register_plugin_hook('unit_test', 'system', 'metadata_test'); +elgg_register_plugin_hook_handler('unit_test', 'system', 'metadata_test'); /** * Metadata unit test @@ -1358,9 +968,11 @@ register_plugin_hook('unit_test', 'system', 'metadata_test'); * @param mixed $params Params * * @return array + * @access private */ function metadata_test($hook, $type, $value, $params) { global $CONFIG; - $value[] = $CONFIG->path . 'engine/tests/objects/metadata.php'; + $value[] = $CONFIG->path . 'engine/tests/api/metadata.php'; + $value[] = $CONFIG->path . 'engine/tests/api/metadata_cache.php'; return $value; } diff --git a/engine/lib/metastrings.php b/engine/lib/metastrings.php index 8a105b822..57d876c06 100644 --- a/engine/lib/metastrings.php +++ b/engine/lib/metastrings.php @@ -8,11 +8,15 @@ */ /** Cache metastrings for a page */ +global $METASTRINGS_CACHE; $METASTRINGS_CACHE = array(); /** Keep a record of strings we know don't exist */ +global $METASTRINGS_DEADNAME_CACHE; $METASTRINGS_DEADNAME_CACHE = array(); + + /** * Return the meta string id for a given tag, or false. * @@ -63,7 +67,7 @@ function get_metastring_id($string, $case_sensitive = TRUE) { } $row = FALSE; - $metaStrings = get_data($query, "entity_row_to_elggstar"); + $metaStrings = get_data($query); if (is_array($metaStrings)) { if (sizeof($metaStrings) > 1) { $ids = array(); @@ -71,7 +75,7 @@ function get_metastring_id($string, $case_sensitive = TRUE) { $ids[] = $metaString->id; } return $ids; - } else { + } else if (isset($metaStrings[0])) { $row = $metaStrings[0]; } } @@ -157,6 +161,7 @@ function add_metastring($string, $case_sensitive = true) { * Delete any orphaned entries in metastrings. This is run by the garbage collector. * * @return bool + * @access private */ function delete_orphaned_metastrings() { global $CONFIG; @@ -197,4 +202,702 @@ function delete_orphaned_metastrings() { )"; return delete_data($query); -}
\ No newline at end of file +} + +/** + * Returns an array of either ElggAnnotation or ElggMetadata objects. + * Accepts all elgg_get_entities() options for entity restraints. + * + * @see elgg_get_entities + * + * @param array $options Array in format: + * + * metastring_names => NULL|ARR metastring names + * + * metastring_values => NULL|ARR metastring values + * + * metastring_ids => NULL|ARR metastring ids + * + * metastring_case_sensitive => BOOL Overall Case sensitive + * + * metastring_owner_guids => NULL|ARR Guids for metadata owners + * + * metastring_created_time_lower => INT Lower limit for created time. + * + * metastring_created_time_upper => INT Upper limit for created time. + * + * metastring_calculation => STR Perform the MySQL function on the metastring values + * returned. + * This differs from egef_annotation_calculation in that + * it returns only the calculation of all annotation values. + * You can sum, avg, count, etc. egef_annotation_calculation() + * returns ElggEntities ordered by a calculation on their + * annotation values. + * + * metastring_type => STR metadata or annotation(s) + * + * @return mixed + * @access private + */ +function elgg_get_metastring_based_objects($options) { + $options = elgg_normalize_metastrings_options($options); + + switch ($options['metastring_type']) { + case 'metadata': + $type = 'metadata'; + $callback = 'row_to_elggmetadata'; + break; + + case 'annotations': + case 'annotation': + $type = 'annotations'; + $callback = 'row_to_elggannotation'; + break; + + default: + return false; + } + + $defaults = array( + // entities + 'types' => ELGG_ENTITIES_ANY_VALUE, + 'subtypes' => ELGG_ENTITIES_ANY_VALUE, + 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE, + + 'guids' => ELGG_ENTITIES_ANY_VALUE, + 'owner_guids' => ELGG_ENTITIES_ANY_VALUE, + 'container_guids' => ELGG_ENTITIES_ANY_VALUE, + 'site_guids' => get_config('site_guid'), + + 'modified_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'modified_time_upper' => ELGG_ENTITIES_ANY_VALUE, + 'created_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'created_time_upper' => ELGG_ENTITIES_ANY_VALUE, + + // options are normalized to the plural in case we ever add support for them. + 'metastring_names' => ELGG_ENTITIES_ANY_VALUE, + 'metastring_values' => ELGG_ENTITIES_ANY_VALUE, + //'metastring_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, + //'metastring_name_value_pairs_operator' => 'AND', + + 'metastring_case_sensitive' => TRUE, + //'order_by_metastring' => array(), + 'metastring_calculation' => ELGG_ENTITIES_NO_VALUE, + + 'metastring_created_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'metastring_created_time_upper' => ELGG_ENTITIES_ANY_VALUE, + + 'metastring_owner_guids' => ELGG_ENTITIES_ANY_VALUE, + + 'metastring_ids' => ELGG_ENTITIES_ANY_VALUE, + + // sql + 'order_by' => 'n_table.time_created asc', + 'limit' => 10, + 'offset' => 0, + 'count' => FALSE, + 'selects' => array(), + 'wheres' => array(), + 'joins' => array(), + + 'callback' => $callback + ); + + // @todo Ignore site_guid right now because of #2910 + $options['site_guid'] = ELGG_ENTITIES_ANY_VALUE; + + $options = array_merge($defaults, $options); + + // can't use helper function with type_subtype_pair because + // it's already an array...just need to merge it + if (isset($options['type_subtype_pair'])) { + if (isset($options['type_subtype_pairs'])) { + $options['type_subtype_pairs'] = array_merge($options['type_subtype_pairs'], + $options['type_subtype_pair']); + } else { + $options['type_subtype_pairs'] = $options['type_subtype_pair']; + } + } + + $singulars = array( + 'type', 'subtype', 'type_subtype_pair', + 'guid', 'owner_guid', 'container_guid', 'site_guid', + 'metastring_name', 'metastring_value', + 'metastring_owner_guid', 'metastring_id', + 'select', 'where', 'join' + ); + + $options = elgg_normalise_plural_options_array($options, $singulars); + + if (!$options) { + return false; + } + + $db_prefix = elgg_get_config('dbprefix'); + + // evaluate where clauses + if (!is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } + + $wheres = $options['wheres']; + + // entities + $wheres[] = elgg_get_entity_type_subtype_where_sql('e', $options['types'], + $options['subtypes'], $options['type_subtype_pairs']); + + $wheres[] = elgg_get_guid_based_where_sql('e.guid', $options['guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.owner_guid', $options['owner_guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.container_guid', $options['container_guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.site_guid', $options['site_guids']); + + $wheres[] = elgg_get_entity_time_where_sql('e', $options['created_time_upper'], + $options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']); + + + $wheres[] = elgg_get_entity_time_where_sql('n_table', $options['metastring_created_time_upper'], + $options['metastring_created_time_lower'], null, null); + + $wheres[] = elgg_get_guid_based_where_sql('n_table.owner_guid', + $options['metastring_owner_guids']); + + // see if any functions failed + // remove empty strings on successful functions + foreach ($wheres as $i => $where) { + if ($where === FALSE) { + return FALSE; + } elseif (empty($where)) { + unset($wheres[$i]); + } + } + + // remove identical where clauses + $wheres = array_unique($wheres); + + // evaluate join clauses + if (!is_array($options['joins'])) { + $options['joins'] = array($options['joins']); + } + + $joins = $options['joins']; + $joins[] = "JOIN {$db_prefix}entities e ON n_table.entity_guid = e.guid"; + + // evaluate selects + if (!is_array($options['selects'])) { + $options['selects'] = array($options['selects']); + } + + $selects = $options['selects']; + + // For performance reasons we don't want the joins required for metadata / annotations + // unless we're going through one of their callbacks. + // this means we expect the functions passing different callbacks to pass their required joins. + // If we're doing a calculation + $custom_callback = ($options['callback'] == 'row_to_elggmetadata' + || $options['callback'] == 'row_to_elggannotation'); + $is_calculation = $options['metastring_calculation'] ? true : false; + + if ($custom_callback || $is_calculation) { + $joins[] = "JOIN {$db_prefix}metastrings n on n_table.name_id = n.id"; + $joins[] = "JOIN {$db_prefix}metastrings v on n_table.value_id = v.id"; + + $selects[] = 'n.string as name'; + $selects[] = 'v.string as value'; + } + + foreach ($joins as $i => $join) { + if ($join === FALSE) { + return FALSE; + } elseif (empty($join)) { + unset($joins[$i]); + } + } + + // metastrings + $metastring_clauses = elgg_get_metastring_sql('n_table', $options['metastring_names'], + $options['metastring_values'], null, $options['metastring_ids'], + $options['metastring_case_sensitive']); + + if ($metastring_clauses) { + $wheres = array_merge($wheres, $metastring_clauses['wheres']); + $joins = array_merge($joins, $metastring_clauses['joins']); + } else { + $wheres[] = get_access_sql_suffix('n_table'); + } + + if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) { + $selects = array_unique($selects); + // evalutate selects + $select_str = ''; + if ($selects) { + foreach ($selects as $select) { + $select_str .= ", $select"; + } + } + + $query = "SELECT DISTINCT n_table.*{$select_str} FROM {$db_prefix}$type n_table"; + } elseif ($options['count']) { + // count is over the entities + $query = "SELECT count(DISTINCT e.guid) as calculation FROM {$db_prefix}$type n_table"; + } else { + $query = "SELECT {$options['metastring_calculation']}(v.string) as calculation FROM {$db_prefix}$type n_table"; + } + + // remove identical join clauses + $joins = array_unique($joins); + + // add joins + foreach ($joins as $j) { + $query .= " $j "; + } + + // add wheres + $query .= ' WHERE '; + + foreach ($wheres as $w) { + $query .= " $w AND "; + } + + // Add access controls + $query .= get_access_sql_suffix('e'); + + // reverse order by + if (isset($options['reverse_order_by']) && $options['reverse_order_by']) { + $options['order_by'] = elgg_sql_reverse_order_by_clause($options['order_by'], + $defaults['order_by']); + } + + if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) { + if (isset($options['group_by'])) { + $options['group_by'] = sanitise_string($options['group_by']); + $query .= " GROUP BY {$options['group_by']}"; + } + + if (isset($options['order_by']) && $options['order_by']) { + $options['order_by'] = sanitise_string($options['order_by']); + $query .= " ORDER BY {$options['order_by']}, n_table.id"; + } + + if ($options['limit']) { + $limit = sanitise_int($options['limit']); + $offset = sanitise_int($options['offset'], false); + $query .= " LIMIT $offset, $limit"; + } + + $dt = get_data($query, $options['callback']); + return $dt; + } else { + $result = get_data_row($query); + return $result->calculation; + } +} + +/** + * Returns an array of joins and wheres for use in metastrings. + * + * @note The $pairs is reserved for name/value pairs if we want to implement those. + * + * @param string $table The annotation or metadata table name or alias + * @param array $names An array of names + * @param array $values An array of values + * @param array $pairs Name / value pairs. Not currently used. + * @param array $ids Metastring IDs + * @param bool $case_sensitive Should name and values be case sensitive? + * + * @return array + * @access private + */ +function elgg_get_metastring_sql($table, $names = null, $values = null, + $pairs = null, $ids = null, $case_sensitive = false) { + + if ((!$names && $names !== 0) + && (!$values && $values !== 0) + && !$ids + && (!$pairs && $pairs !== 0)) { + + return array(); + } + + $db_prefix = elgg_get_config('dbprefix'); + + // binary forces byte-to-byte comparision of strings, making + // it case- and diacritical-mark- sensitive. + // only supported on values. + $binary = ($case_sensitive) ? ' BINARY ' : ''; + + $return = array ( + 'joins' => array (), + 'wheres' => array() + ); + + $wheres = array(); + + // get names wheres and joins + $names_where = ''; + if ($names !== NULL) { + if (!is_array($names)) { + $names = array($names); + } + + $sanitised_names = array(); + foreach ($names as $name) { + // normalise to 0. + if (!$name) { + $name = '0'; + } + $sanitised_names[] = '\'' . sanitise_string($name) . '\''; + } + + if ($names_str = implode(',', $sanitised_names)) { + $return['joins'][] = "JOIN {$db_prefix}metastrings msn on $table.name_id = msn.id"; + $names_where = "(msn.string IN ($names_str))"; + } + } + + // get values wheres and joins + $values_where = ''; + if ($values !== NULL) { + if (!is_array($values)) { + $values = array($values); + } + + $sanitised_values = array(); + foreach ($values as $value) { + // normalize to 0 + if (!$value) { + $value = 0; + } + $sanitised_values[] = '\'' . sanitise_string($value) . '\''; + } + + if ($values_str = implode(',', $sanitised_values)) { + $return['joins'][] = "JOIN {$db_prefix}metastrings msv on $table.value_id = msv.id"; + $values_where = "({$binary}msv.string IN ($values_str))"; + } + } + + if ($ids !== NULL) { + if (!is_array($ids)) { + $ids = array($ids); + } + + $ids_str = implode(',', $ids); + + if ($ids_str) { + $wheres[] = "n_table.id IN ($ids_str)"; + } + } + + if ($names_where && $values_where) { + $wheres[] = "($names_where AND $values_where)"; + } elseif ($names_where) { + $wheres[] = $names_where; + } elseif ($values_where) { + $wheres[] = $values_where; + } + + $wheres[] = get_access_sql_suffix($table); + + if ($where = implode(' AND ', $wheres)) { + $return['wheres'][] = "($where)"; + } + + return $return; +} + +/** + * Normalizes metadata / annotation option names to their corresponding metastrings name. + * + * @param array $options An options array + * @since 1.8.0 + * @return array + * @access private + */ +function elgg_normalize_metastrings_options(array $options = array()) { + + // support either metastrings_type or metastring_type + // because I've made this mistake many times and hunting it down is a pain... + $type = elgg_extract('metastring_type', $options, null); + $type = elgg_extract('metastrings_type', $options, $type); + + $options['metastring_type'] = $type; + + // support annotation_ and annotations_ because they're way too easy to confuse + $prefixes = array('metadata_', 'annotation_', 'annotations_'); + + // map the metadata_* options to metastring_* options + $map = array( + 'names' => 'metastring_names', + 'values' => 'metastring_values', + 'case_sensitive' => 'metastring_case_sensitive', + 'owner_guids' => 'metastring_owner_guids', + 'created_time_lower' => 'metastring_created_time_lower', + 'created_time_upper' => 'metastring_created_time_upper', + 'calculation' => 'metastring_calculation', + 'ids' => 'metastring_ids' + ); + + foreach ($prefixes as $prefix) { + $singulars = array("{$prefix}name", "{$prefix}value", "{$prefix}owner_guid", "{$prefix}id"); + $options = elgg_normalise_plural_options_array($options, $singulars); + + foreach ($map as $specific => $normalized) { + $key = $prefix . $specific; + if (isset($options[$key])) { + $options[$normalized] = $options[$key]; + } + } + } + + return $options; +} + +/** + * Enables or disables a metastrings-based object by its id. + * + * @warning To enable disabled metastrings you must first use + * {@link access_show_hidden_entities()}. + * + * @param int $id The object's ID + * @param string $enabled Value to set to: yes or no + * @param string $type The type of table to use: metadata or annotations + * + * @return bool + * @throws InvalidParameterException + * @since 1.8.0 + * @access private + */ +function elgg_set_metastring_based_object_enabled_by_id($id, $enabled, $type) { + $id = (int)$id; + $db_prefix = elgg_get_config('dbprefix'); + + $object = elgg_get_metastring_based_object_from_id($id, $type); + + switch($type) { + case 'annotation': + case 'annotations': + $table = "{$db_prefix}annotations"; + break; + + case 'metadata': + $table = "{$db_prefix}metadata"; + break; + } + + if ($enabled === 'yes' || $enabled === 1 || $enabled === true) { + $enabled = 'yes'; + $event = 'enable'; + } elseif ($enabled === 'no' || $enabled === 0 || $enabled === false) { + $enabled = 'no'; + $event = 'disable'; + } else { + return false; + } + + $return = false; + + if ($object) { + // don't set it if it's already set. + if ($object->enabled == $enabled) { + $return = false; + } elseif ($object->canEdit() && (elgg_trigger_event($event, $type, $object))) { + $return = update_data("UPDATE $table SET enabled = '$enabled' where id = $id"); + } + } + + return $return; +} + +/** + * Runs metastrings-based objects found using $options through $callback + * + * @warning Unlike elgg_get_metastring_based_objects() this will not accept an + * empty options array! + * + * @warning This returns null on no ops. + * + * @param array $options An options array. {@See elgg_get_metastring_based_objects()} + * @param string $callback The callback to pass each result through + * @param bool $inc_offset Increment the offset? Pass false for callbacks that delete / disable + * + * @return bool|null true on success, false on failure, null if no objects are found. + * @since 1.8.0 + * @access private + */ +function elgg_batch_metastring_based_objects(array $options, $callback, $inc_offset = true) { + if (!$options || !is_array($options)) { + return false; + } + + $batch = new ElggBatch('elgg_get_metastring_based_objects', $options, $callback, 50, $inc_offset); + return $batch->callbackResult; +} + +/** + * Returns a singular metastring-based object by its ID. + * + * @param int $id The metastring-based object's ID + * @param string $type The type: annotation or metadata + * @return ElggMetadata|ElggAnnotation + * + * @since 1.8.0 + * @access private + */ +function elgg_get_metastring_based_object_from_id($id, $type) { + $id = (int)$id; + if (!$id) { + return false; + } + + $options = array( + 'metastring_type' => $type, + 'metastring_id' => $id + ); + + $obj = elgg_get_metastring_based_objects($options); + + if ($obj && count($obj) == 1) { + return $obj[0]; + } + + return false; +} + +/** + * Deletes a metastring-based object by its id + * + * @param int $id The object's ID + * @param string $type The object's metastring type: annotation or metadata + * @return bool + * + * @since 1.8.0 + * @access private + */ +function elgg_delete_metastring_based_object_by_id($id, $type) { + $id = (int)$id; + $db_prefix = elgg_get_config('dbprefix'); + + switch ($type) { + case 'annotation': + case 'annotations': + $type = 'annotations'; + break; + + case 'metadata': + $type = 'metadata'; + break; + + default: + return false; + } + + $obj = elgg_get_metastring_based_object_from_id($id, $type); + $table = $db_prefix . $type; + + if ($obj) { + // Tidy up if memcache is enabled. + // @todo only metadata is supported + if ($type == 'metadata') { + static $metabyname_memcache; + if ((!$metabyname_memcache) && (is_memcache_available())) { + $metabyname_memcache = new ElggMemcache('metabyname_memcache'); + } + + if ($metabyname_memcache) { + // @todo why name_id? is that even populated? + $metabyname_memcache->delete("{$obj->entity_guid}:{$obj->name_id}"); + } + } + + if (($obj->canEdit()) && (elgg_trigger_event('delete', $type, $obj))) { + return (bool)delete_data("DELETE from $table where id=$id"); + } + } + + return false; +} + +/** + * Entities interface helpers + */ + +/** + * Returns options to pass to elgg_get_entities() for metastrings operations. + * + * @param string $type Metastring type: annotations or metadata + * @param array $options Options + * + * @return array + * @since 1.7.0 + * @access private + */ +function elgg_entities_get_metastrings_options($type, $options) { + $valid_types = array('metadata', 'annotation'); + if (!in_array($type, $valid_types)) { + return FALSE; + } + + // the options for annotations are singular (annotation_name) but the table + // is plural (elgg_annotations) so rewrite for the table name. + $n_table = ($type == 'annotation') ? 'annotations' : $type; + + $singulars = array("{$type}_name", "{$type}_value", + "{$type}_name_value_pair", "{$type}_owner_guid"); + $options = elgg_normalise_plural_options_array($options, $singulars); + + $clauses = elgg_get_entity_metadata_where_sql('e', $n_table, $options["{$type}_names"], + $options["{$type}_values"], $options["{$type}_name_value_pairs"], + $options["{$type}_name_value_pairs_operator"], $options["{$type}_case_sensitive"], + $options["order_by_{$type}"], $options["{$type}_owner_guids"]); + + if ($clauses) { + // merge wheres to pass to get_entities() + if (isset($options['wheres']) && !is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } elseif (!isset($options['wheres'])) { + $options['wheres'] = array(); + } + + $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']); + + // merge joins to pass to get_entities() + if (isset($options['joins']) && !is_array($options['joins'])) { + $options['joins'] = array($options['joins']); + } elseif (!isset($options['joins'])) { + $options['joins'] = array(); + } + + $options['joins'] = array_merge($options['joins'], $clauses['joins']); + + if ($clauses['orders']) { + $order_by_metadata = implode(", ", $clauses['orders']); + if (isset($options['order_by']) && $options['order_by']) { + $options['order_by'] = "$order_by_metadata, {$options['order_by']}"; + } else { + $options['order_by'] = "$order_by_metadata, e.time_created DESC"; + } + } + } + + return $options; +} + +// unit testing +elgg_register_plugin_hook_handler('unit_test', 'system', 'metastrings_test'); + +/** + * Metadata unit test + * + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of other tests + * @param mixed $params Params + * + * @return array + * @access private + */ +function metastrings_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/metastrings.php'; + return $value; +} diff --git a/engine/lib/navigation.php b/engine/lib/navigation.php index 75c5958f4..ab9cc05e8 100644 --- a/engine/lib/navigation.php +++ b/engine/lib/navigation.php @@ -3,504 +3,525 @@ * Elgg navigation library * Functions for managing menus and other navigational elements * + * Breadcrumbs + * Elgg uses a breadcrumb stack. The page handlers (controllers in MVC terms) + * push the breadcrumb links onto the stack. @see elgg_push_breadcrumb() + * + * + * Pagination + * Automatically handled by Elgg when using elgg_list_entities* functions. + * @see elgg_list_entities() + * + * + * Tabs + * @see navigation/tabs view + * + * + * Menus + * Elgg uses a single interface to manage its menus. Menu items are added with + * {@link elgg_register_menu_item()}. This is generally used for menus that + * appear only once per page. For dynamic menus (such as the hover + * menu for user's avatar), a plugin hook is emitted when the menu is being + * created. The hook is 'register', 'menu:<menu_name>'. For more details on this, + * @see elgg_view_menu(). + * + * Menus supported by the Elgg core + * Standard menus: + * site Site navigation shown on every page. + * page Page menu usually shown in a sidebar. Uses Elgg's context. + * topbar Topbar menu shown on every page. The default has two sections. + * footer Like the topbar but in the footer. + * extras Links about content on the page. The RSS link is added to this. + * + * Dynamic menus (also called just-in-time menus): + * user_hover Avatar hover menu. The user entity is passed as a parameter. + * entity The set of links shown in the summary of an entity. + * river Links shown on river items. + * owner_block Links shown for a user or group in their owner block. + * filter The tab filter for content (all, mine, friends) + * title The buttons shown next to a content title. + * long-text The links shown above the input/longtext view. + * * @package Elgg.Core * @subpackage Navigation */ /** - * Deprecated by elgg_add_submenu_item() - * - * @see elgg_add_submenu_item() - * @deprecated 1.8 - * - * @param string $label The label - * @param string $link The link - * @param string $group The group to store item in - * @param boolean $onclick Add a confirmation when clicked? - * @param boolean $selected Is menu item selected + * Register an item for an Elgg menu + * + * @warning Generally you should not use this in response to the plugin hook: + * 'register', 'menu:<menu_name>'. If you do, you may end up with many incorrect + * links on a dynamic menu. + * + * @warning A menu item's name must be unique per menu. If more than one menu + * item with the same name are registered, the last menu item takes priority. + * + * @see elgg_view_menu() for the plugin hooks available for modifying a menu as + * it is being rendered. + * + * @param string $menu_name The name of the menu: site, page, userhover, + * userprofile, groupprofile, or any custom menu + * @param mixed $menu_item A ElggMenuItem object or an array of options in format: + * name => STR Menu item identifier (required) + * text => STR Menu item display text (required) + * href => STR Menu item URL (required) (false for non-links. + * @warning If you disable the href the <a> tag will + * not appear, so the link_class will not apply. If you + * put <a> tags in manually through the 'text' option + * the default CSS selector .elgg-menu-$menu > li > a + * may affect formatting. Wrap in a <span> if it does.) + * contexts => ARR Page context strings + * section => STR Menu section identifier + * title => STR Menu item tooltip + * selected => BOOL Is this menu item currently selected + * parent_name => STR Identifier of the parent menu item + * link_class => STR A class or classes for the <a> tag + * item_class => STR A class or classes for the <li> tag + * + * Additional options that the view output/url takes can be + * passed in the array. If the 'confirm' key is passed, the + * menu link uses the 'output/confirmlink' view. Custom + * options can be added by using the 'data' key with the + * value being an associative array. * * @return bool + * @since 1.8.0 */ -function add_submenu_item($label, $link, $group = 'default', $onclick = false, $selected = NULL) { - elgg_deprecated_notice('add_submenu_item was deprecated by elgg_add_submenu_item', 1.8); - - $item = array( - 'text' => $label, - 'href' => $link, - 'selected' => $selected - ); +function elgg_register_menu_item($menu_name, $menu_item) { + global $CONFIG; - if (!$group) { - $group = 'default'; + if (!isset($CONFIG->menus[$menu_name])) { + $CONFIG->menus[$menu_name] = array(); } - if ($onclick) { - $js = "onclick=\"javascript:return confirm('" . elgg_echo('deleteconfirm') . "')\""; - $item['vars'] = array('js' => $js); - } - // submenu items were added in the page setup hook usually by checking - // the context. We'll pass in the current context here, which will - // emulate that effect. - // if context == 'main' (default) it probably means they always wanted - // the menu item to show up everywhere. - $context = elgg_get_context(); - - if ($context == 'main') { - $context = 'all'; + if (is_array($menu_item)) { + $item = ElggMenuItem::factory($menu_item); + if (!$item) { + elgg_log("Unable to add menu item '{$menu_item['name']}' to '$menu_name' menu", 'WARNING'); + elgg_log(print_r($menu_item, true), 'DEBUG'); + return false; + } + } else { + $item = $menu_item; } - return elgg_add_submenu_item($item, $context, $group); + + $CONFIG->menus[$menu_name][] = $item; + return true; } /** - * Add an entry to the submenu. - * - * @param array $item The item as: - * <code> - * array( - * 'title' => 'Text to display', - * 'url' => 'URL of the link', - * 'id' => 'entry_unique_id' //used by children items to identify parents - * 'parent_id' => 'id_of_parent', - * 'selected' => BOOL // Is this item selected? (If NULL or unset will attempt to guess) - * 'vars' => array() // Array of vars to pass to the navigation/submenu_item view - * ) - * </code> + * Remove an item from a menu * - * @param string $context Context in which to display this menu item. 'all' - * will make it show up all the time. Use sparingly. - * @param string $group Group for the item. Each submenu group has its own <ul> + * @param string $menu_name The name of the menu + * @param string $item_name The unique identifier for this menu item * - * @return BOOL - * @since 1.8 - * @see elgg_prepare_submenu + * @return bool + * @since 1.8.0 */ -function elgg_add_submenu_item(array $item, $context = 'all', $group = 'default') { +function elgg_unregister_menu_item($menu_name, $item_name) { global $CONFIG; - if (!isset($CONFIG->submenu_items)) { - $CONFIG->submenu_items = array(); - } - - if (!isset($CONFIG->submenu_items[$context])) { - $CONFIG->submenu_items[$context] = array(); - } - - if (!isset($CONFIG->submenu_items[$context][$group])) { - $CONFIG->submenu_items[$context][$group] = array(); + if (!isset($CONFIG->menus[$menu_name])) { + return false; } - if (!isset($item['text'])) { - return FALSE; - } - - if (!empty($item['href'])) { - $item['href'] = elgg_normalize_url($item['href']); - } - - // we use persistent object properties in the submenu - // setup function, so normalize the array to an object. - // we pass it in as an array because this would be the only - // place in elgg that we ask for an object like this. - // consistency ftw. - $item_obj = new StdClass(); - - foreach ($item as $k => $v) { - switch ($k) { - case 'parent_id': - case 'id': - // make sure '' and false make sense - $v = (empty($v)) ? NULL : $v; - - default: - $item_obj->$k = $v; - break; + foreach ($CONFIG->menus[$menu_name] as $index => $menu_object) { + /* @var ElggMenuItem $menu_object */ + if ($menu_object->getName() == $item_name) { + unset($CONFIG->menus[$menu_name][$index]); + return true; } } - $CONFIG->submenu_items[$context][$group][] = $item_obj; - - return TRUE; + return false; } /** - * Properly nest all submenu entries for contexts $context and 'all' - * - * @param string $context Context for menus - * @param bool $sort Sort the menu items alphabetically + * Check if a menu item has been registered * - * @since 1.8 - * @see elgg_add_submenu_item - * - * @return true + * @param string $menu_name The name of the menu + * @param string $item_name The unique identifier for this menu item + * + * @return bool + * @since 1.8.0 */ -function elgg_prepare_submenu($context = 'main', $sort = FALSE) { +function elgg_is_menu_item_registered($menu_name, $item_name) { global $CONFIG; - if (!isset($CONFIG->submenu_items) || !($CONFIG->submenu_items)) { - return FALSE; + if (!isset($CONFIG->menus[$menu_name])) { + return false; } - $groups = array(); - - if (isset($CONFIG->submenu_items['all'])) { - $groups = $CONFIG->submenu_items['all']; + foreach ($CONFIG->menus[$menu_name] as $menu_object) { + /* @var ElggMenuItem $menu_object */ + if ($menu_object->getName() == $item_name) { + return true; + } } - if (isset($CONFIG->submenu_items[$context])) { - $groups = array_merge_recursive($groups, $CONFIG->submenu_items[$context]); - } + return false; +} - if (!$groups) { - return FALSE; - } +/** + * Convenience function for registering a button to title menu + * + * The URL must be $handler/$name/$guid where $guid is the guid of the page owner. + * The label of the button is "$handler:$name" so that must be defined in a + * language file. + * + * This is used primarily to support adding an add content button + * + * @param string $handler The handler to use or null to autodetect from context + * @param string $name Name of the button + * @return void + * @since 1.8.0 + */ +function elgg_register_title_button($handler = null, $name = 'add') { + if (elgg_is_logged_in()) { - foreach ($groups as $group => $items) { - if ($sort) { - usort($items, 'elgg_submenu_item_cmp'); + if (!$handler) { + $handler = elgg_get_context(); } - $parsed_menu = array(); - // determin which children need to go in this item. - foreach ($items as $i => $item) { - // can only support children if there's an id - if (isset($item->id)) { - foreach ($items as $child_i => $child_item) { - // don't check ourselves or used children. - if ($child_i == $i || $child_item->used == TRUE) { - continue; - } - - if (isset($child_item->parent_id) && $child_item->parent_id == $item->id) { - if (!isset($item->children)) { - $item->children = array(); - } - $item->children[] = $child_item; - $child_item->parent = $item; - // don't unset because we still need to check this item for children - $child_item->used = TRUE; - } - } - - // if the parent doesn't have a url, make it the first child item. - if (isset($item->children) && $item->children && !$item->href) { - $child = $item->children[0]; - while ($child && !isset($child->href)) { - if (isset($child->children) && isset($child->children[0])) { - $child = $child->children[0]; - } else { - $child = NULL; - } - } - - if ($child && isset($child->href)) { - $item->href = $child->href; - } else { - // @todo There are no URLs anywhere in this tree. - $item->href = elgg_get_site_url(); - } - } - } - - // only add top-level elements to the menu. - // the rest are children. - if (!isset($item->parent_id)) { - $parsed_menu[] = $item; - } + $owner = elgg_get_page_owner_entity(); + if (!$owner) { + // no owns the page so this is probably an all site list page + $owner = elgg_get_logged_in_user_entity(); + } + if ($owner && $owner->canWriteToContainer()) { + $guid = $owner->getGUID(); + elgg_register_menu_item('title', array( + 'name' => $name, + 'href' => "$handler/$name/$guid", + 'text' => elgg_echo("$handler:$name"), + 'link_class' => 'elgg-button elgg-button-action', + )); } - - $CONFIG->submenu[$context][$group] = $parsed_menu; } - - return TRUE; } /** - * Helper function used to sort submenu items by their display text. + * Adds a breadcrumb to the breadcrumbs stack. + * + * @param string $title The title to display + * @param string $link Optional. The link for the title. * - * @param object $a First object - * @param object $b Second object + * @return void + * @since 1.8.0 * - * @return int - * @since 1.8 - * @see elgg_prepare_submenu + * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs */ -function elgg_submenu_item_cmp($a, $b) { - $a = $a->text; - $b = $b->text; +function elgg_push_breadcrumb($title, $link = NULL) { + global $CONFIG; + if (!isset($CONFIG->breadcrumbs)) { + $CONFIG->breadcrumbs = array(); + } - return strnatcmp($a, $b); + // avoid key collisions. + $CONFIG->breadcrumbs[] = array('title' => elgg_get_excerpt($title, 100), 'link' => $link); } /** - * Use elgg_get_submenu(). - * - * @see elgg_get_submenu() - * @deprecated 1.8 + * Removes last breadcrumb entry. * - * @return string + * @return array popped item. + * @since 1.8.0 + * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs */ -function get_submenu() { - elgg_deprecated_notice("get_submenu() has been deprecated by elgg_get_submenu()", 1.8); - return elgg_get_submenu(); +function elgg_pop_breadcrumb() { + global $CONFIG; + + if (is_array($CONFIG->breadcrumbs)) { + return array_pop($CONFIG->breadcrumbs); + } + + return FALSE; } /** - * Return the HTML for a sidemenu. - * - * @param string $context The context of the submenu (defaults to main) - * @param BOOL $sort Sort by display name? + * Returns all breadcrumbs as an array of array('title' => 'Readable Title', 'link' => 'URL') * - * @return string Formatted HTML. - * @since 1.8 - * @todo Rename to a view function. See {@trac #2320}. + * @return array Breadcrumbs + * @since 1.8.0 + * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs */ -function elgg_get_submenu($context = NULL, $sort = FALSE) { +function elgg_get_breadcrumbs() { global $CONFIG; - if (!$context) { - $context = elgg_get_context(); + if (isset($CONFIG->breadcrumbs) && is_array($CONFIG->breadcrumbs)) { + return $CONFIG->breadcrumbs; } - if (!elgg_prepare_submenu($context, $sort)) { - return ''; - } - - $groups = $CONFIG->submenu[$context]; - $submenu_html = ''; - - foreach ($groups as $group => $items) { - // how far down we are in children arrays - $depth = 0; - // push and pop parent items - $temp_items = array(); + return array(); +} - while ($item = current($items)) { - // ignore parents created by a child but parent never defined properly - if (!isset($item->text) || !($item->text)) { - next($items); - continue; +/** + * Set up the site menu + * + * Handles default, featured, and custom menu items + * + * @param string $hook + * @param string $type + * @param array $return Menu array + * @param array $params + * @return array + * @access private + */ +function elgg_site_menu_setup($hook, $type, $return, $params) { + + $featured_menu_names = elgg_get_config('site_featured_menu_names'); + $custom_menu_items = elgg_get_config('site_custom_menu_items'); + if ($featured_menu_names || $custom_menu_items) { + // we have featured or custom menu items + + $registered = $return['default']; + + // set up featured menu items + $featured = array(); + foreach ($featured_menu_names as $name) { + foreach ($registered as $index => $item) { + if ($item->getName() == $name) { + $featured[] = $item; + unset($registered[$index]); + } } + } - // try to guess if this should be selected if they don't specify - if ((!isset($item->selected) || $item->selected === NULL) && isset($item->href)) { - $item->selected = elgg_http_url_is_identical(full_url(), $item->href); - } + // add custom menu items + $n = 1; + foreach ($custom_menu_items as $title => $url) { + $item = new ElggMenuItem("custom$n", $title, $url); + $featured[] = $item; + $n++; + } - // traverse up the parent tree if matached to mark all parents as selected/expanded. - if ($item->selected && isset($item->parent)) { - $parent = $item->parent; - while ($parent) { - $parent->selected = TRUE; - if (isset($parent->parent)) { - $parent = $parent->parent; - } else { - $parent = NULL; - } - } + $return['default'] = $featured; + if (count($registered) > 0) { + $return['more'] = $registered; + } + } else { + // no featured menu items set + $max_display_items = 5; + + // the first n are shown, rest added to more list + // if only one item on more menu, stick it with the rest + $num_menu_items = count($return['default']); + if ($num_menu_items > ($max_display_items + 1)) { + $return['more'] = array_splice($return['default'], $max_display_items); + } + } + + // check if we have anything selected + $selected = false; + foreach ($return as $section) { + foreach ($section as $item) { + if ($item->getSelected()) { + $selected = true; + break 2; } - - // get the next item - if (isset($item->children) && $item->children) { - $depth++; - array_push($temp_items, $items); - $items = $item->children; - } elseif ($depth > 0) { - // check if there are more children elements in the current items - // pop back up to the parent(s) if not - if ($item = next($items)) { - continue; - } else { - while ($depth > 0) { - $depth--; - $items = array_pop($temp_items); - if ($item = next($items)) { - break; - } + } + } + + if (!$selected) { + // nothing selected, match name to context or match url + $current_url = current_page_url(); + foreach ($return as $section_name => $section) { + foreach ($section as $key => $item) { + // only highlight internal links + if (strpos($item->getHref(), elgg_get_site_url()) === 0) { + if ($item->getName() == elgg_get_context()) { + $return[$section_name][$key]->setSelected(true); + break 2; + } + if ($item->getHref() == $current_url) { + $return[$section_name][$key]->setSelected(true); + break 2; } } - } else { - next($items); } } - - $vars = array('group' => $group, 'items' => $items); - $submenu_html .= elgg_view('navigation/submenu_group', $vars); } - // include the JS for the expand menus too - return elgg_view('navigation/submenu_js') . $submenu_html; -} - -/** - * Registers any custom menu items with the main Site Menu. - * - * @note Custom menu items are added through the admin interface. Plugins - * can add standard menu items by using {@link add_menu()}. - * - * @since 1.8 - * @link http://docs.elgg.org/Tutorials/UI/SiteMenu - * @elgg_event_handler init system - * @return void - */ -function add_custom_menu_items() { - if ($custom_items = get_config('menu_items_custom_items')) { - foreach ($custom_items as $url => $name) { - add_menu($name, $url); - } - } + return $return; } /** - * Returns the main site menu. - * - * @note The main site menu is split into "featured" links and - * "more" links. - * - * @return array ('featured_urls' and 'more') - * @since 1.8 - * @link http://docs.elgg.org/Tutorials/UI/SiteMenu + * Add the comment and like links to river actions menu + * @access private */ -function elgg_get_nav_items() { - $menu_items = get_register('menu'); - $featured_urls_info = get_config('menu_items_featured_urls'); - - $more = array(); - $featured_urls = array(); - $featured_urls_sanitised = array(); - - // easier to compare with in_array() than embedded foreach()es - $valid_urls = array(); - foreach ($menu_items as $info) { - $valid_urls[] = $info->value->url; - } - - // make sure the url is a valid link. - // this prevents disabled plugins leaving behind - // valid links when not using a pagehandler. - if ($featured_urls_info) { - foreach ($featured_urls_info as $info) { - if (in_array($info->value->url, $valid_urls)) { - $featured_urls[] = $info->value->url; - $featured_urls_sanitised[] = $info; +function elgg_river_menu_setup($hook, $type, $return, $params) { + if (elgg_is_logged_in()) { + $item = $params['item']; + /* @var ElggRiverItem $item */ + $object = $item->getObjectEntity(); + // comments and non-objects cannot be commented on or liked + if (!elgg_in_context('widgets') && $item->annotation_id == 0) { + // comments + if ($object->canComment()) { + $options = array( + 'name' => 'comment', + 'href' => "#comments-add-$object->guid", + 'text' => elgg_view_icon('speech-bubble'), + 'title' => elgg_echo('comment:this'), + 'rel' => 'toggle', + 'priority' => 50, + ); + $return[] = ElggMenuItem::factory($options); } } - } - - // add toolbar entries if not hiding dupes. - foreach ($menu_items as $name => $info) { - if (!in_array($info->value->url, $featured_urls)) { - $more[] = $info; + + if (elgg_is_admin_logged_in()) { + $options = array( + 'name' => 'delete', + 'href' => elgg_add_action_tokens_to_url("action/river/delete?id=$item->id"), + 'text' => elgg_view_icon('delete'), + 'title' => elgg_echo('delete'), + 'confirm' => elgg_echo('deleteconfirm'), + 'priority' => 200, + ); + $return[] = ElggMenuItem::factory($options); } } - return array( - 'featured' => $featured_urls_sanitised, - 'more' => $more - ); + return $return; } /** - * Adds an item to the site-wide menu. - * - * You can obtain the menu array by calling {@link get_register('menu')} - * - * @param string $menu_name The name of the menu item - * @param string $menu_url The URL of the page - * @param array $menu_children Optionally, an array of submenu items (not currently used) - * @param string $context The context of the menu - * - * @return true|false Depending on success - * @todo Can be deprecated when the new menu system is introduced. + * Entity menu is list of links and info on any entity + * @access private */ -function add_menu($menu_name, $menu_url, $menu_children = array(), $context = "") { - global $CONFIG; - - if (!isset($CONFIG->menucontexts)) { - $CONFIG->menucontexts = array(); +function elgg_entity_menu_setup($hook, $type, $return, $params) { + if (elgg_in_context('widgets')) { + return $return; } - - if (empty($context)) { - $context = get_plugin_name(); + + $entity = $params['entity']; + /* @var ElggEntity $entity */ + $handler = elgg_extract('handler', $params, false); + + // access + $access = elgg_view('output/access', array('entity' => $entity)); + $options = array( + 'name' => 'access', + 'text' => $access, + 'href' => false, + 'priority' => 100, + ); + $return[] = ElggMenuItem::factory($options); + + if ($entity->canEdit() && $handler) { + // edit link + $options = array( + 'name' => 'edit', + 'text' => elgg_echo('edit'), + 'title' => elgg_echo('edit:this'), + 'href' => "$handler/edit/{$entity->getGUID()}", + 'priority' => 200, + ); + $return[] = ElggMenuItem::factory($options); + + // delete link + $options = array( + 'name' => 'delete', + 'text' => elgg_view_icon('delete'), + 'title' => elgg_echo('delete:this'), + 'href' => "action/$handler/delete?guid={$entity->getGUID()}", + 'confirm' => elgg_echo('deleteconfirm'), + 'priority' => 300, + ); + $return[] = ElggMenuItem::factory($options); } - $value = new stdClass(); - $value->url = elgg_normalize_url($menu_url); - $value->context = $context; - - $CONFIG->menucontexts[] = $context; - return add_to_register('menu', $menu_name, $value, $menu_children); + return $return; } /** - * Removes an item from the menu register - * - * @param string $menu_name The name of the menu item - * - * @return true|false Depending on success - */ -function remove_menu($menu_name) { - return remove_from_register('menu', $menu_name); -} - -/** - * Returns a menu item for use in the children section of add_menu() - * This is not currently used in the Elgg core. - * - * @param string $menu_name The name of the menu item - * @param string $menu_url Its URL - * - * @return stdClass|false Depending on success - * @todo Can be deprecated when the new menu system is introduced. - */ -function menu_item($menu_name, $menu_url) { - elgg_deprecated_notice('menu_item() is deprecated by add_submenu_item', 1.7); - return make_register_object($menu_name, $menu_url); -} - -/** - * Adds a breadcrumb to the breadcrumbs stack. - * - * @param string $title The title to display - * @param string $link Optional. The link for the title. - * - * @return void - * - * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs + * Widget menu is a set of widget controls + * @access private */ -function elgg_push_breadcrumb($title, $link = NULL) { - global $CONFIG; - if (!is_array($CONFIG->breadcrumbs)) { - $CONFIG->breadcrumbs = array(); +function elgg_widget_menu_setup($hook, $type, $return, $params) { + + $widget = $params['entity']; + /* @var ElggWidget $widget */ + $show_edit = elgg_extract('show_edit', $params, true); + + $collapse = array( + 'name' => 'collapse', + 'text' => ' ', + 'href' => "#elgg-widget-content-$widget->guid", + 'class' => 'elgg-widget-collapse-button', + 'rel' => 'toggle', + 'priority' => 1 + ); + $return[] = ElggMenuItem::factory($collapse); + + if ($widget->canEdit()) { + $delete = array( + 'name' => 'delete', + 'text' => elgg_view_icon('delete-alt'), + 'title' => elgg_echo('widget:delete', array($widget->getTitle())), + 'href' => "action/widgets/delete?widget_guid=$widget->guid", + 'is_action' => true, + 'class' => 'elgg-widget-delete-button', + 'id' => "elgg-widget-delete-button-$widget->guid", + 'priority' => 900 + ); + $return[] = ElggMenuItem::factory($delete); + + if ($show_edit) { + $edit = array( + 'name' => 'settings', + 'text' => elgg_view_icon('settings-alt'), + 'title' => elgg_echo('widget:edit'), + 'href' => "#widget-edit-$widget->guid", + 'class' => "elgg-widget-edit-button", + 'rel' => 'toggle', + 'priority' => 800, + ); + $return[] = ElggMenuItem::factory($edit); + } } - // avoid key collisions. - $CONFIG->breadcrumbs[] = array('title' => $title, 'link' => $link); + return $return; } /** - * Removes last breadcrumb entry. - * - * @return array popped item. - * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs + * Adds a delete link to "generic_comment" annotations + * @access private */ -function elgg_pop_breadcrumb() { - global $CONFIG; - - if (is_array($CONFIG->breadcrumbs)) { - array_pop($CONFIG->breadcrumbs); +function elgg_annotation_menu_setup($hook, $type, $return, $params) { + $annotation = $params['annotation']; + /* @var ElggAnnotation $annotation */ + + if ($annotation->name == 'generic_comment' && $annotation->canEdit()) { + $url = elgg_http_add_url_query_elements('action/comments/delete', array( + 'annotation_id' => $annotation->id, + )); + + $options = array( + 'name' => 'delete', + 'href' => $url, + 'text' => "<span class=\"elgg-icon elgg-icon-delete\"></span>", + 'confirm' => elgg_echo('deleteconfirm'), + 'encode_text' => false + ); + $return[] = ElggMenuItem::factory($options); } - return FALSE; + return $return; } + /** - * Returns all breadcrumbs as an array of array('title' => 'Readable Title', 'link' => 'URL') - * - * @return array Breadcrumbs - * @link http://docs.elgg.org/Tutorials/UI/Breadcrumbs + * Navigation initialization + * @access private */ -function elgg_get_breadcrumbs() { - global $CONFIG; - - return (is_array($CONFIG->breadcrumbs)) ? $CONFIG->breadcrumbs : array(); +function elgg_nav_init() { + elgg_register_plugin_hook_handler('prepare', 'menu:site', 'elgg_site_menu_setup'); + elgg_register_plugin_hook_handler('register', 'menu:river', 'elgg_river_menu_setup'); + elgg_register_plugin_hook_handler('register', 'menu:entity', 'elgg_entity_menu_setup'); + elgg_register_plugin_hook_handler('register', 'menu:widget', 'elgg_widget_menu_setup'); + elgg_register_plugin_hook_handler('register', 'menu:annotation', 'elgg_annotation_menu_setup'); } + +elgg_register_event_handler('init', 'system', 'elgg_nav_init'); diff --git a/engine/lib/notification.php b/engine/lib/notification.php index cfb79715c..be0c359d4 100644 --- a/engine/lib/notification.php +++ b/engine/lib/notification.php @@ -19,6 +19,7 @@ */ /** Notification handlers */ +global $NOTIFICATION_HANDLERS; $NOTIFICATION_HANDLERS = array(); /** @@ -37,7 +38,7 @@ $NOTIFICATION_HANDLERS = array(); function register_notification_handler($method, $handler, $params = NULL) { global $NOTIFICATION_HANDLERS; - if (is_callable($handler)) { + if (is_callable($handler, true)) { $NOTIFICATION_HANDLERS[$method] = new stdClass; $NOTIFICATION_HANDLERS[$method]->handler = $handler; @@ -85,7 +86,7 @@ function unregister_notification_handler($method) { * @throws NotificationException */ function notify_user($to, $from, $subject, $message, array $params = NULL, $methods_override = "") { - global $NOTIFICATION_HANDLERS, $CONFIG; + global $NOTIFICATION_HANDLERS; // Sanitise if (!is_array($to)) { @@ -109,12 +110,15 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth // Are we overriding delivery? $methods = $methods_override; if (!$methods) { - $tmp = (array)get_user_notification_settings($guid); + $tmp = get_user_notification_settings($guid); $methods = array(); - foreach ($tmp as $k => $v) { - // Add method if method is turned on for user! - if ($v) { - $methods[] = $k; + // $tmp may be false. don't cast + if (is_object($tmp)) { + foreach ($tmp as $k => $v) { + // Add method if method is turned on for user! + if ($v) { + $methods[] = $k; + } } } } @@ -130,16 +134,17 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth // Extract method details from list $details = $NOTIFICATION_HANDLERS[$method]; $handler = $details->handler; + /* @var callable $handler */ - if ((!$NOTIFICATION_HANDLERS[$method]) || (!$handler)) { - error_log(sprintf(elgg_echo('NotificationException:NoHandlerFound'), $method)); + if ((!$NOTIFICATION_HANDLERS[$method]) || (!$handler) || (!is_callable($handler))) { + error_log(elgg_echo('NotificationException:NoHandlerFound', array($method))); } elgg_log("Sending message to $guid using $method"); // Trigger handler and retrieve result. try { - $result[$guid][$method] = $handler( + $result[$guid][$method] = call_user_func($handler, $from ? get_entity($from) : NULL, // From entity get_entity($guid), // To entity $subject, // The subject @@ -163,16 +168,21 @@ function notify_user($to, $from, $subject, $message, array $params = NULL, $meth * * @param int $user_guid The user id * - * @return stdClass + * @return stdClass|false */ function get_user_notification_settings($user_guid = 0) { $user_guid = (int)$user_guid; if ($user_guid == 0) { - $user_guid = get_loggedin_userid(); + $user_guid = elgg_get_logged_in_user_guid(); } - $all_metadata = get_metadata_for_entity($user_guid); + // @todo: there should be a better way now that metadata is cached. E.g. just query for MD names, then + // query user object directly + $all_metadata = elgg_get_metadata(array( + 'guid' => $user_guid, + 'limit' => 0 + )); if ($all_metadata) { $prefix = "notification:method:"; $return = new stdClass; @@ -207,7 +217,7 @@ function set_user_notification_setting($user_guid, $method, $value) { $user = get_entity($user_guid); if (!$user) { - $user = get_loggedin_user(); + $user = elgg_get_logged_in_user_entity(); } if (($user) && ($user instanceof ElggUser)) { @@ -231,6 +241,8 @@ function set_user_notification_setting($user_guid, $method, $value) { * @param array $params Optional parameters (none taken in this instance) * * @return bool + * @throws NotificationException + * @access private */ function email_notify_handler(ElggEntity $from, ElggUser $to, $subject, $message, array $params = NULL) { @@ -238,17 +250,17 @@ array $params = NULL) { global $CONFIG; if (!$from) { - $msg = sprintf(elgg_echo('NotificationException:MissingParameter'), 'from'); + $msg = elgg_echo('NotificationException:MissingParameter', array('from')); throw new NotificationException($msg); } if (!$to) { - $msg = sprintf(elgg_echo('NotificationException:MissingParameter'), 'to'); + $msg = elgg_echo('NotificationException:MissingParameter', array('to')); throw new NotificationException($msg); } if ($to->email == "") { - $msg = sprintf(elgg_echo('NotificationException:NoEmailAddress'), $to->guid); + $msg = elgg_echo('NotificationException:NoEmailAddress', array($to->guid)); throw new NotificationException($msg); } @@ -256,7 +268,7 @@ array $params = NULL) { $to = $to->email; // From - $site = get_entity($CONFIG->site_guid); + $site = elgg_get_site_entity(); // If there's an email address, use it - but only if its not from a user. if (!($from instanceof ElggUser) && $from->email) { $from = $from->email; @@ -281,18 +293,19 @@ array $params = NULL) { * @param array $params Optional parameters (none used in this function) * * @return bool + * @throws NotificationException * @since 1.7.2 */ function elgg_send_email($from, $to, $subject, $body, array $params = NULL) { global $CONFIG; if (!$from) { - $msg = sprintf(elgg_echo('NotificationException:NoEmailAddress'), 'from'); + $msg = elgg_echo('NotificationException:MissingParameter', array('from')); throw new NotificationException($msg); } if (!$to) { - $msg = sprintf(elgg_echo('NotificationException:NoEmailAddress'), 'to'); + $msg = elgg_echo('NotificationException:MissingParameter', array('to')); throw new NotificationException($msg); } @@ -305,7 +318,7 @@ function elgg_send_email($from, $to, $subject, $body, array $params = NULL) { 'params' => $params ); - $result = trigger_plugin_hook('email', 'system', $mail_params, NULL); + $result = elgg_trigger_plugin_hook('email', 'system', $mail_params, NULL); if ($result !== NULL) { return $result; } @@ -337,6 +350,8 @@ function elgg_send_email($from, $to, $subject, $body, array $params = NULL) { // Sanitise subject by stripping line endings $subject = preg_replace("/(\r\n|\r|\n)/", " ", $subject); + // this is because Elgg encodes everything and matches what is done with body + $subject = html_entity_decode($subject, ENT_COMPAT, 'UTF-8'); // Decode any html entities if (is_callable('mb_encode_mimeheader')) { $subject = mb_encode_mimeheader($subject, "UTF-8", "B"); } @@ -354,15 +369,16 @@ function elgg_send_email($from, $to, $subject, $body, array $params = NULL) { * Correctly initialise notifications and register the email handler. * * @return void + * @access private */ function notification_init() { // Register a notification handler for the default email method register_notification_handler("email", "email_notify_handler"); // Add settings view to user settings & register action - extend_elgg_settings_page('notifications/settings/usersettings', 'usersettings/user'); + elgg_extend_view('forms/account/settings', 'core/settings/account/notifications'); - register_plugin_hook('usersettings:save', 'user', 'notification_user_settings_save'); + elgg_register_plugin_hook_handler('usersettings:save', 'user', 'notification_user_settings_save'); } /** @@ -370,9 +386,11 @@ function notification_init() { * * @return void * @todo why can't this call action(...)? + * @access private */ function notification_user_settings_save() { global $CONFIG; + //@todo Wha?? include($CONFIG->path . "actions/notifications/settings/usersettings/save.php"); } @@ -412,7 +430,7 @@ function register_notification_object($entity_type, $object_subtype, $language_n * @param int $user_guid The GUID of the user who wants to follow a user's content * @param int $author_guid The GUID of the user whose content the user wants to follow * - * @return true|false Depending on success + * @return bool Depending on success */ function register_notification_interest($user_guid, $author_guid) { return add_entity_relationship($user_guid, 'notify', $author_guid); @@ -424,7 +442,7 @@ function register_notification_interest($user_guid, $author_guid) { * @param int $user_guid The GUID of the user who is following a user's content * @param int $author_guid The GUID of the user whose content the user wants to unfollow * - * @return true|false Depending on success + * @return bool Depending on success */ function remove_notification_interest($user_guid, $author_guid) { return remove_entity_relationship($user_guid, 'notify', $author_guid); @@ -440,16 +458,18 @@ function remove_notification_interest($user_guid, $author_guid) { * @param string $object_type mixed * @param mixed $object The object created * - * @return void + * @return bool + * @access private */ function object_notifications($event, $object_type, $object) { // We only want to trigger notification events for ElggEntities if ($object instanceof ElggEntity) { + /* @var ElggEntity $object */ // Get config data global $CONFIG, $SESSION, $NOTIFICATION_HANDLERS; - $hookresult = trigger_plugin_hook('object:notifications', $object_type, array( + $hookresult = elgg_trigger_plugin_hook('object:notifications', $object_type, array( 'event' => $event, 'object_type' => $object_type, 'object' => $object, @@ -470,35 +490,37 @@ function object_notifications($event, $object_type, $object) { } if (isset($CONFIG->register_objects[$object_type][$object_subtype])) { - $descr = $CONFIG->register_objects[$object_type][$object_subtype]; - $string = $descr . ": " . $object->getURL(); + $subject = $CONFIG->register_objects[$object_type][$object_subtype]; + $string = $subject . ": " . $object->getURL(); // Get users interested in content from this person and notify them // (Person defined by container_guid so we can also subscribe to groups if we want) foreach ($NOTIFICATION_HANDLERS as $method => $foo) { $interested_users = elgg_get_entities_from_relationship(array( + 'site_guids' => ELGG_ENTITIES_ANY_VALUE, 'relationship' => 'notify' . $method, 'relationship_guid' => $object->container_guid, 'inverse_relationship' => TRUE, - 'types' => 'user', - 'limit' => 99999 + 'type' => 'user', + 'limit' => false )); + /* @var ElggUser[] $interested_users */ if ($interested_users && is_array($interested_users)) { foreach ($interested_users as $user) { if ($user instanceof ElggUser && !$user->isBanned()) { if (($user->guid != $SESSION['user']->guid) && has_access_to_entity($object, $user) && $object->access_id != ACCESS_PRIVATE) { - $methodstring = trigger_plugin_hook('notify:entity:message', $object->getType(), array( + $body = elgg_trigger_plugin_hook('notify:entity:message', $object->getType(), array( 'entity' => $object, 'to_entity' => $user, 'method' => $method), $string); - if (empty($methodstring) && $methodstring !== false) { - $methodstring = $string; + if (empty($body) && $body !== false) { + $body = $string; } - if ($methodstring !== false) { - notify_user($user->guid, $object->container_guid, $descr, $methodstring, - NULL, array($method)); + if ($body !== false) { + notify_user($user->guid, $object->container_guid, $subject, $body, + null, array($method)); } } } @@ -510,5 +532,5 @@ function object_notifications($event, $object_type, $object) { } // Register a startup event -register_elgg_event_handler('init', 'system', 'notification_init', 0); -register_elgg_event_handler('create', 'object', 'object_notifications');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'notification_init', 0); +elgg_register_event_handler('create', 'object', 'object_notifications'); diff --git a/engine/lib/objects.php b/engine/lib/objects.php index ae30edb13..ff3cc733f 100644 --- a/engine/lib/objects.php +++ b/engine/lib/objects.php @@ -13,6 +13,7 @@ * @param int $guid The guid to retreive * * @return bool + * @access private */ function get_object_entity_as_row($guid) { global $CONFIG; @@ -30,6 +31,7 @@ function get_object_entity_as_row($guid) { * @param string $description The object's description * * @return bool + * @access private */ function create_object_entity($guid, $title, $description) { global $CONFIG; @@ -51,11 +53,8 @@ function create_object_entity($guid, $title, $description) { if ($result != false) { // Update succeeded, continue $entity = get_entity($guid); - if (trigger_elgg_event('update', $entity->type, $entity)) { - return $guid; - } else { - $entity->delete(); - } + elgg_trigger_event('update', $entity->type, $entity); + return $guid; } } else { // Update failed, attempt an insert. @@ -65,7 +64,7 @@ function create_object_entity($guid, $title, $description) { $result = insert_data($query); if ($result !== false) { $entity = get_entity($guid); - if (trigger_elgg_event('create', $entity->type, $entity)) { + if (elgg_trigger_event('create', $entity->type, $entity)) { return $guid; } else { $entity->delete(); @@ -78,76 +77,6 @@ function create_object_entity($guid, $title, $description) { } /** - * THIS FUNCTION IS DEPRECATED. - * - * Delete a object's extra data. - * - * @todo - this should be removed - was deprecated in 1.5 or earlier - * - * @param int $guid GUID - * - * @return 1 - */ -function delete_object_entity($guid) { - system_message(sprintf(elgg_echo('deprecatedfunction'), 'delete_user_entity')); - - return 1; // Always return that we have deleted one row in order to not break existing code. -} - -/** - * Searches for an object based on a complete or partial title - * or description using full text searching. - * - * IMPORTANT NOTE: With MySQL's default setup: - * 1) $criteria must be 4 or more characters long - * 2) If $criteria matches greater than 50% of results NO RESULTS ARE RETURNED! - * - * @param string $criteria The partial or full name or username. - * @param int $limit Limit of the search. - * @param int $offset Offset. - * @param string $order_by The order. - * @param boolean $count Whether to return the count of results or just the results. - * - * @return int|false - * @deprecated 1.7 - */ -function search_for_object($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { - elgg_deprecated_notice('search_for_object() was deprecated by new search plugin.', 1.7); - global $CONFIG; - - $criteria = sanitise_string($criteria); - $limit = (int)$limit; - $offset = (int)$offset; - $order_by = sanitise_string($order_by); - $container_guid = (int)$container_guid; - - $access = get_access_sql_suffix("e"); - - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - - if ($count) { - $query = "SELECT count(e.guid) as total "; - } else { - $query = "SELECT e.* "; - } - $query .= "from {$CONFIG->dbprefix}entities e - join {$CONFIG->dbprefix}objects_entity o on e.guid=o.guid - where match(o.title,o.description) against ('$criteria') and $access"; - - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } - } - return false; -} - -/** * Get the sites this object is part of * * @param int $object_guid The object's GUID @@ -164,21 +93,22 @@ function get_object_sites($object_guid, $limit = 10, $offset = 0) { return elgg_get_entities_from_relationship(array( 'relationship' => 'member_of_site', 'relationship_guid' => $object_guid, - 'types' => 'site', + 'type' => 'site', 'limit' => $limit, - 'offset' => $offset + 'offset' => $offset, )); } /** * Runs unit tests for ElggObject * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params * * @return array + * @access private */ function objects_test($hook, $type, $value, $params) { global $CONFIG; @@ -186,43 +116,5 @@ function objects_test($hook, $type, $value, $params) { return $value; } - -/** - * Returns a formatted list of objects suitable for injecting into search. - * - * @deprecated 1.7 - * - * @param sting $hook Hook - * @param string $user user - * @param mixed $returnvalue Previous return value - * @param mixed $tag Search term - * - * @return array - */ -function search_list_objects_by_name($hook, $user, $returnvalue, $tag) { - elgg_deprecated_notice('search_list_objects_by_name was deprecated by new search plugin.', 1.7); - - // Change this to set the number of users that display on the search page - $threshold = 4; - - $object = get_input('object'); - - if (!get_input('offset') && (empty($object) || $object == 'user')) { - if ($users = search_for_user($tag, $threshold)) { - $countusers = search_for_user($tag, 0, 0, "", true); - - $return = elgg_view('user/search/startblurb', array('count' => $countusers, 'tag' => $tag)); - foreach ($users as $user) { - $return .= elgg_view_entity($user); - } - $return .= elgg_view('user/search/finishblurb', - array('count' => $countusers, 'threshold' => $threshold, 'tag' => $tag)); - - return $return; - - } - } -} - -register_elgg_event_handler('init', 'system', 'objects_init', 0); -register_plugin_hook('unit_test', 'system', 'objects_test');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'objects_init', 0); +elgg_register_plugin_hook_handler('unit_test', 'system', 'objects_test'); diff --git a/engine/lib/opendd.php b/engine/lib/opendd.php index d856806f5..7d635a295 100644 --- a/engine/lib/opendd.php +++ b/engine/lib/opendd.php @@ -7,12 +7,15 @@ * @version 0.4 */ +// @codingStandardsIgnoreStart + /** * Attempt to construct an ODD object out of a XmlElement or sub-elements. * * @param XmlElement $element The element(s) * * @return mixed An ODD object if the element can be handled, or false. + * @access private */ function ODD_factory (XmlElement $element) { $name = $element->name; @@ -57,6 +60,7 @@ function ODD_factory (XmlElement $element) { * @param string $xml The XML ODD. * * @return ODDDocument + * @access private */ function ODD_Import($xml) { // Parse XML to an array @@ -96,7 +100,10 @@ function ODD_Import($xml) { * @param ODDDocument $document The Document. * * @return string + * @access private */ function ODD_Export(ODDDocument $document) { return "$document"; -}
\ No newline at end of file +} + +// @codingStandardsIgnoreEnd diff --git a/engine/lib/output.php b/engine/lib/output.php index 35ace7c09..de4f911fb 100644 --- a/engine/lib/output.php +++ b/engine/lib/output.php @@ -12,29 +12,34 @@ * * @param string $text The input string * - * @return string The output stirng with formatted links - **/ + * @return string The output string with formatted links + */ function parse_urls($text) { + + // URI specification: http://www.ietf.org/rfc/rfc3986.txt + // This varies from the specification in the following ways: + // * Supports non-ascii characters + // * Does not allow parentheses and single quotes + // * Cuts off commas, exclamation points, and periods off as last character + // @todo this causes problems with <attr = "val"> - // must be ing <attr="val"> format (no space). + // must be in <attr="val"> format (no space). // By default htmlawed rewrites tags to this format. // if PHP supported conditional negative lookbehinds we could use this: // $r = preg_replace_callback('/(?<!=)(?<![ ])?(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i', - // - // we can put , in the list of excluded char but need to keep . because of domain names. - // it is removed in the callback. - $r = preg_replace_callback('/(?<!=)(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i', + $r = preg_replace_callback('/(?<![=\/"\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\']+)/i', create_function( '$matches', ' $url = $matches[1]; - $period = \'\'; - if (substr($url, -1, 1) == \'.\') { - $period = \'.\'; - $url = trim($url, \'.\'); + $punc = ""; + $last = substr($url, -1, 1); + if (in_array($last, array(".", "!", ",", "(", ")"))) { + $punc = $last; + $url = rtrim($url, ".!,()"); } $urltext = str_replace("/", "/<wbr />", $url); - return "<a href=\"$url\" style=\"text-decoration:underline;\">$urltext</a>$period"; + return "<a href=\"$url\" rel=\"nofollow\">$urltext</a>$punc"; ' ), $text); @@ -43,51 +48,26 @@ function parse_urls($text) { /** * Create paragraphs from text with line spacing - * Borrowed from Wordpress. * * @param string $pee The string - * @param bool $br Add BRs? + * @deprecated Use elgg_autop instead + * @todo Add deprecation warning in 1.9 * - * @todo Rewrite * @return string **/ -function autop($pee, $br = 1) { - $pee = $pee . "\n"; // just to make things a little easier, pad the end - $pee = preg_replace('|<br />\s*<br />|', "\n\n", $pee); - // Space things out a little - $allblocks = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|map|area|blockquote|address|math|style|input|p|h[1-6]|hr)'; - $pee = preg_replace('!(<' . $allblocks . '[^>]*>)!', "\n$1", $pee); - $pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee); - $pee = str_replace(array("\r\n", "\r"), "\n", $pee); // cross-platform newlines - if ( strpos($pee, '<object') !== false ) { - $pee = preg_replace('|\s*<param([^>]*)>\s*|', "<param$1>", $pee); // no pee inside object/embed - $pee = preg_replace('|\s*</embed>\s*|', '</embed>', $pee); - } - $pee = preg_replace("/\n\n+/", "\n\n", $pee); // take care of duplicates - $pee = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "<p>$1</p>\n", $pee); // make paragraphs, including one at the end - $pee = preg_replace('|<p>\s*?</p>|', '', $pee); // under certain strange conditions it could create a P of entirely whitespace - $pee = preg_replace('!<p>([^<]+)\s*?(</(?:div|address|form)[^>]*>)!', "<p>$1</p>$2", $pee); - $pee = preg_replace( '|<p>|', "$1<p>", $pee ); - $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); // don't pee all over a tag - $pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee); // problem with nested lists - $pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee); - $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee); - $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee); - $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); - if ($br) { - $pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', create_function('$matches', 'return str_replace("\n", "<WPPreserveNewline />", $matches[0]);'), $pee); - $pee = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $pee); // optionally make line breaks - $pee = str_replace('<WPPreserveNewline />', "\n", $pee); - } - $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*<br />!', "$1", $pee); - $pee = preg_replace('!<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee); -// if (strpos($pee, '<pre') !== false) { -// mind the space between the ? and >. Only there because of the comment. -// $pee = preg_replace_callback('!(<pre.*? >)(.*?)</pre>!is', 'clean_pre', $pee ); -// } - $pee = preg_replace( "|\n</p>$|", '</p>', $pee ); - - return $pee; +function autop($pee) { + return elgg_autop($pee); +} + +/** + * Create paragraphs from text with line spacing + * + * @param string $string The string + * + * @return string + **/ +function elgg_autop($string) { + return ElggAutoP::getInstance()->process($string); } /** @@ -140,48 +120,156 @@ function elgg_format_url($url) { } /** - * Converts shorthand urls to absolute urls. - * + * Converts an associative array into a string of well-formed attributes + * + * @note usually for HTML, but could be useful for XML too... + * + * @param array $attrs An associative array of attr => val pairs + * + * @return string HTML attributes to be inserted into a tag (e.g., <tag $attrs>) + */ +function elgg_format_attributes(array $attrs) { + $attrs = elgg_clean_vars($attrs); + $attributes = array(); + + if (isset($attrs['js'])) { + //@todo deprecated notice? + + if (!empty($attrs['js'])) { + $attributes[] = $attrs['js']; + } + + unset($attrs['js']); + } + + foreach ($attrs as $attr => $val) { + $attr = strtolower($attr); + + if ($val === TRUE) { + $val = $attr; //e.g. checked => TRUE ==> checked="checked" + } + + // ignore $vars['entity'] => ElggEntity stuff + if ($val !== NULL && $val !== false && (is_array($val) || !is_object($val))) { + + // allow $vars['class'] => array('one', 'two'); + // @todo what about $vars['style']? Needs to be semi-colon separated... + if (is_array($val)) { + $val = implode(' ', $val); + } + + $val = htmlspecialchars($val, ENT_QUOTES, 'UTF-8', false); + $attributes[] = "$attr=\"$val\""; + } + } + + return implode(' ', $attributes); +} + +/** + * Preps an associative array for use in {@link elgg_format_attributes()}. + * + * Removes all the junk that {@link elgg_view()} puts into $vars. + * Maintains backward compatibility with attributes like 'internalname' and 'internalid' + * + * @note This function is called automatically by elgg_format_attributes(). No need to + * call it yourself before using elgg_format_attributes(). + * + * @param array $vars The raw $vars array with all it's dirtiness (config, url, etc.) + * + * @return array The array, ready to be used in elgg_format_attributes(). + * @access private + */ +function elgg_clean_vars(array $vars = array()) { + unset($vars['config']); + unset($vars['url']); + unset($vars['user']); + + // backwards compatibility code + if (isset($vars['internalname'])) { + $vars['name'] = $vars['internalname']; + unset($vars['internalname']); + } + + if (isset($vars['internalid'])) { + $vars['id'] = $vars['internalid']; + unset($vars['internalid']); + } + + if (isset($vars['__ignoreInternalid'])) { + unset($vars['__ignoreInternalid']); + } + + if (isset($vars['__ignoreInternalname'])) { + unset($vars['__ignoreInternalname']); + } + + return $vars; +} + +/** + * Converts shorthand urls to absolute urls. + * * If the url is already absolute or protocol-relative, no change is made. - * - * @example + * + * @example * elgg_normalize_url(''); // 'http://my.site.com/' - * elgg_normalize_url('pg/dashboard'); // 'http://my.site.com/pg/dashboard' + * elgg_normalize_url('dashboard'); // 'http://my.site.com/dashboard' * elgg_normalize_url('http://google.com/'); // no change * elgg_normalize_url('//google.com/'); // no change - * - * @param string $url The URL to normalize - * + * + * @param string $url The URL to normalize + * * @return string The absolute url */ function elgg_normalize_url($url) { - // 'http://example.com', 'https://example.com', '//example.com' - if (preg_match("#^(https?:)?//#i", $url)) { + // see https://bugs.php.net/bug.php?id=51192 + // from the bookmarks save action. + $php_5_2_13_and_below = version_compare(PHP_VERSION, '5.2.14', '<'); + $php_5_3_0_to_5_3_2 = version_compare(PHP_VERSION, '5.3.0', '>=') && + version_compare(PHP_VERSION, '5.3.3', '<'); + + if ($php_5_2_13_and_below || $php_5_3_0_to_5_3_2) { + $tmp_address = str_replace("-", "", $url); + $validated = filter_var($tmp_address, FILTER_VALIDATE_URL); + } else { + $validated = filter_var($url, FILTER_VALIDATE_URL); + } + + // work around for handling absoluate IRIs (RFC 3987) - see #4190 + if (!$validated && (strpos($url, 'http:') === 0) || (strpos($url, 'https:') === 0)) { + $validated = true; + } + + if ($validated) { + // all normal URLs including mailto: + return $url; + + } elseif (preg_match("#^(\#|\?|//)#i", $url)) { + // '//example.com' (Shortcut for protocol.) + // '?query=test', #target return $url; - } - // 'example.com', 'example.com/subpage' - elseif (preg_match("#[^/]*\.[^/]*/?#i", $url)) { + } elseif (stripos($url, 'javascript:') === 0 || stripos($url, 'mailto:') === 0) { + // 'javascript:' and 'mailto:' + // Not covered in FILTER_VALIDATE_URL + return $url; + + } elseif (preg_match("#^[^/]*\.php(\?.*)?$#i", $url)) { + // 'install.php', 'install.php?step=step' + return elgg_get_site_url() . $url; + + } elseif (preg_match("#^[^/]*\.#i", $url)) { + // 'example.com', 'example.com/subpage' return "http://$url"; - } - - // 'pg/page/handler' - else { - return elgg_get_site_url().$url; - } -} -/** - * When given a title, returns a version suitable for inclusion in a URL - * - * @param string $title The title - * - * @return string The optimised title - * @deprecated 1.8 - */ -function friendly_title($title) { - elgg_deprecated_notice('friendly_title was deprecated by elgg_get_friendly_title', 1.8); - return elgg_get_friendly_title($title); + } else { + // 'page/handler', 'mod/plugin/file.php' + + // trim off any leading / because the site URL is stored + // with a trailing / + return elgg_get_site_url() . ltrim($url, '/'); + } } /** @@ -196,31 +284,17 @@ function elgg_get_friendly_title($title) { // return a URL friendly title to short circuit normal title formatting $params = array('title' => $title); - $result = trigger_plugin_hook('format', 'friendly:title', $params, NULL); + $result = elgg_trigger_plugin_hook('format', 'friendly:title', $params, NULL); if ($result) { return $result; } - //$title = iconv('UTF-8', 'ASCII//TRANSLIT', $title); - $title = preg_replace("/[^\w ]/", "", $title); - $title = str_replace(" ", "-", $title); - $title = str_replace("--", "-", $title); - $title = trim($title); - $title = strtolower($title); - return $title; -} + // titles are often stored HTML encoded + $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8'); + + $title = ElggTranslit::urlize($title); -/** - * Displays a UNIX timestamp in a friendly way (eg "less than a minute ago") - * - * @param int $time A UNIX epoch timestamp - * - * @return string The friendly time - * @deprecated 1.8 - */ -function friendly_time($time) { - elgg_deprecated_notice('friendly_time was deprecated by elgg_view_friendly_time', 1.8); - return elgg_view_friendly_time($time); + return $title; } /** @@ -237,7 +311,7 @@ function elgg_get_friendly_time($time) { // return a time string to short circuit normal time formatting $params = array('time' => $time); - $result = trigger_plugin_hook('format', 'friendly:time', $params, NULL); + $result = elgg_trigger_plugin_hook('format', 'friendly:time', $params, NULL); if ($result) { return $result; } @@ -257,9 +331,9 @@ function elgg_get_friendly_time($time) { } if ($diff > 1) { - return sprintf(elgg_echo("friendlytime:minutes"), $diff); + return elgg_echo("friendlytime:minutes", array($diff)); } else { - return sprintf(elgg_echo("friendlytime:minutes:singular"), $diff); + return elgg_echo("friendlytime:minutes:singular", array($diff)); } } else if ($diff < $day) { $diff = round($diff / $hour); @@ -268,9 +342,9 @@ function elgg_get_friendly_time($time) { } if ($diff > 1) { - return sprintf(elgg_echo("friendlytime:hours"), $diff); + return elgg_echo("friendlytime:hours", array($diff)); } else { - return sprintf(elgg_echo("friendlytime:hours:singular"), $diff); + return elgg_echo("friendlytime:hours:singular", array($diff)); } } else { $diff = round($diff / $day); @@ -279,9 +353,9 @@ function elgg_get_friendly_time($time) { } if ($diff > 1) { - return sprintf(elgg_echo("friendlytime:days"), $diff); + return elgg_echo("friendlytime:days", array($diff)); } else { - return sprintf(elgg_echo("friendlytime:days:singular"), $diff); + return elgg_echo("friendlytime:days:singular", array($diff)); } } } @@ -289,7 +363,7 @@ function elgg_get_friendly_time($time) { /** * Strip tags and offer plugins the chance. * Plugins register for output:strip_tags plugin hook. - * Original string included in $params['original_string'] + * Original string included in $params['original_string'] * * @param string $string Formatted string * @@ -299,73 +373,97 @@ function elgg_strip_tags($string) { $params['original_string'] = $string; $string = strip_tags($string); - $string = trigger_plugin_hook('format', 'strip_tags', $params, $string); + $string = elgg_trigger_plugin_hook('format', 'strip_tags', $params, $string); return $string; } /** - * Filters a string into an array of significant words - * - * @deprecated 1.8 - * - * @param string $string A string - * - * @return array - */ -function filter_string($string) { - elgg_deprecated_notice('filter_string() was deprecated!', 1.8); - - // Convert it to lower and trim - $string = strtolower($string); - $string = trim($string); - - // Remove links and email addresses - // match protocol://address/path/file.extension?some=variable&another=asf% - $string = preg_replace("/\s([a-zA-Z]+:\/\/[a-z][a-z0-9\_\.\-]*[a-z]{2,6}" - . "[a-zA-Z0-9\/\*\-\?\&\%\=]*)([\s|\.|\,])/iu", " ", $string); - - // match www.something.domain/path/file.extension?some=variable&another=asf% - $string = preg_replace("/\s(www\.[a-z][a-z0-9\_\.\-]*[a-z]{2,6}" - . "[a-zA-Z0-9\/\*\-\?\&\%\=]*)([\s|\.|\,])/iu", " ", $string); - - // match name@address - $string = preg_replace("/\s([a-zA-Z][a-zA-Z0-9\_\.\-]*[a-zA-Z]" - . "*\@[a-zA-Z][a-zA-Z0-9\_\.\-]*[a-zA-Z]{2,6})([\s|\.|\,])/iu", " ", $string); - - // Sanitise the string; remove unwanted characters - $string = preg_replace('/\W/ui', ' ', $string); - - // Explode it into an array - $terms = explode(' ', $string); - - // Remove any blacklist terms - //$terms = array_filter($terms, 'remove_blacklist'); - - return $terms; + * Apply html_entity_decode() to a string while re-entitising HTML + * special char entities to prevent them from being decoded back to their + * unsafe original forms. + * + * This relies on html_entity_decode() not translating entities when + * doing so leaves behind another entity, e.g. &gt; if decoded would + * create > which is another entity itself. This seems to escape the + * usual behaviour where any two paired entities creating a HTML tag are + * usually decoded, i.e. a lone > is not decoded, but <foo> would + * be decoded to <foo> since it creates a full tag. + * + * Note: This function is poorly explained in the manual - which is really + * bad given its potential for misuse on user input already escaped elsewhere. + * Stackoverflow is littered with advice to use this function in the precise + * way that would lead to user input being capable of injecting arbitrary HTML. + * + * @param string $string + * + * @return string + * + * @author Pádraic Brady + * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com) + * @license Released under dual-license GPL2/MIT by explicit permission of Pádraic Brady + * + * @access private + */ +function _elgg_html_decode($string) { + $string = str_replace( + array('>', '<', '&', '"', '''), + array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'), + $string + ); + $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8'); + $string = str_replace( + array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'), + array('>', '<', '&', '"', '''), + $string + ); + return $string; } /** - * Returns true if the word in $input is considered significant + * Prepares query string for output to prevent CSRF attacks. + * + * @param string $string + * @return string * - * @deprecated 1.8 + * @access private + */ +function _elgg_get_display_query($string) { + //encode <,>,&, quotes and characters above 127 + if (function_exists('mb_convert_encoding')) {
+ $display_query = mb_convert_encoding($string, 'HTML-ENTITIES', 'UTF-8');
+ } else {
+ // if no mbstring extension, we just strip characters
+ $display_query = preg_replace("/[^\x01-\x7F]/", "", $string);
+ }
+ return htmlspecialchars($display_query, ENT_QUOTES, 'UTF-8', false); +} + +/** + * Unit tests for Output * - * @param string $input A word + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params * - * @return true|false + * @return array + * @access private */ -function remove_blacklist($input) { - elgg_deprecated_notice('remove_blacklist() was deprecated!', 1.8); - +function output_unit_test($hook, $type, $value, $params) { global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/output.php'; + return $value; +} - if (!is_array($CONFIG->wordblacklist)) { - return $input; - } - - if (strlen($input) < 3 || in_array($input, $CONFIG->wordblacklist)) { - return false; - } +/** + * Initialise the Output subsystem. + * + * @return void + * @access private + */ +function output_init() { + elgg_register_plugin_hook_handler('unit_test', 'system', 'output_unit_test'); +} - return true; -}
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'output_init'); diff --git a/engine/lib/pagehandler.php b/engine/lib/pagehandler.php index 727ace54d..0cf99b6fe 100644 --- a/engine/lib/pagehandler.php +++ b/engine/lib/pagehandler.php @@ -7,14 +7,17 @@ */ /** - * Turns the current page over to the page handler, allowing registered handlers to take over. + * Routes the request to a registered page handler * - * If a page handler returns FALSE, the request is handed over to the default_page_handler. + * This function sets the context based on the handler name (first segment of the + * URL). It also triggers a plugin hook 'route', $handler so that plugins can + * modify the routing or handle a request. * * @param string $handler The name of the handler type (eg 'blog') * @param array $page The parameters to the page, as an array (exploded by '/' slashes) * - * @return true|false Depending on whether a registered page handler was found + * @return bool + * @access private */ function page_handler($handler, $page) { global $CONFIG; @@ -27,57 +30,64 @@ function page_handler($handler, $page) { array_pop($page); } - if (!isset($CONFIG->pagehandler) || empty($handler)) { - $result = false; - } else if (isset($CONFIG->pagehandler[$handler]) && is_callable($CONFIG->pagehandler[$handler])) { - $function = $CONFIG->pagehandler[$handler]; - $result = $function($page, $handler); - if ($result !== false) { - $result = true; - } - } else { - $result = false; + // return false to stop processing the request (because you handled it) + // return a new $request array if you want to route the request differently + $request = array( + 'handler' => $handler, + 'segments' => $page, + ); + $request = elgg_trigger_plugin_hook('route', $handler, null, $request); + if ($request === false) { + return true; } - if (!$result) { - $result = default_page_handler($page, $handler); - } - if ($result !== false) { - $result = true; + $handler = $request['handler']; + $page = $request['segments']; + + $result = false; + if (isset($CONFIG->pagehandler) + && !empty($handler) + && isset($CONFIG->pagehandler[$handler]) + && is_callable($CONFIG->pagehandler[$handler])) { + $function = $CONFIG->pagehandler[$handler]; + $result = call_user_func($function, $page, $handler); } - return $result; + return $result || headers_sent(); } /** * Registers a page handler for a particular identifier * * For example, you can register a function called 'blog_page_handler' for handler type 'blog' - * For all URLs http://yoururl/pg/blog/*, the blog_page_handler() function will be called. + * For all URLs http://yoururl/blog/*, the blog_page_handler() function will be called. * The part of the URL marked with * above will be exploded on '/' characters and passed as an * array to that function. * For example, the URL http://yoururl/blog/username/friends/ would result in the call: * blog_page_handler(array('username','friends'), blog); * - * Page handler functions should return true or the default page handler will be called. - * * A request to register a page handler with the same identifier as previously registered * handler will replace the previous one. * * The context is set to the page handler identifier before the registered * page handler function is called. For the above example, the context is set to 'blog'. * + * Page handlers should return true to indicate that they handled the request. + * Requests not handled are forwarded to the front page with a reason of 404. + * Plugins can register for the 'forward', '404' plugin hook. @see forward() + * * @param string $handler The page type to handle * @param string $function Your function name * - * @return true|false Depending on success + * @return bool Depending on success */ -function register_page_handler($handler, $function) { +function elgg_register_page_handler($handler, $function) { global $CONFIG; + if (!isset($CONFIG->pagehandler)) { $CONFIG->pagehandler = array(); } - if (is_callable($function)) { + if (is_callable($function, true)) { $CONFIG->pagehandler[$handler] = $function; return true; } @@ -88,14 +98,14 @@ function register_page_handler($handler, $function) { /** * Unregister a page handler for an identifier * - * Note: to replace a page handler, call register_page_handler() + * Note: to replace a page handler, call elgg_register_page_handler() * * @param string $handler The page type identifier * * @since 1.7.2 * @return void */ -function unregister_page_handler($handler) { +function elgg_unregister_page_handler($handler) { global $CONFIG; if (!isset($CONFIG->pagehandler)) { @@ -106,35 +116,35 @@ function unregister_page_handler($handler) { } /** - * A default page handler - * Tries to locate a suitable file to include. Only works for core pages, not plugins. + * Serve an error page * - * @param array $page The page URL elements - * @param string $handler The base handler + * @todo not sending status codes yet * - * @return true|false Depending on success + * @param string $hook The name of the hook + * @param string $type The type of the hook + * @param bool $result The current value of the hook + * @param array $params Parameters related to the hook + * @return void */ -function default_page_handler($page, $handler) { - global $CONFIG; - - $page = implode('/', $page); - - // protect against including arbitary files - $page = str_replace("..", "", $page); - - $callpath = $CONFIG->path . $handler . "/" . $page; - if (is_dir($callpath)) { - $callpath = sanitise_filepath($callpath); - $callpath .= "index.php"; - if (file_exists($callpath)) { - if (include($callpath)) { - return TRUE; - } - } - } else if (file_exists($callpath)) { - include($callpath); - return TRUE; +function elgg_error_page_handler($hook, $type, $result, $params) { + if (elgg_view_exists("errors/$type")) { + $content = elgg_view("errors/$type", $params); + } else { + $content = elgg_view("errors/default", $params); } + $body = elgg_view_layout('error', array('content' => $content)); + echo elgg_view_page('', $body, 'error'); + exit; +} - return FALSE; +/** + * Initializes the page handler/routing system + * + * @return void + * @access private + */ +function page_handler_init() { + elgg_register_plugin_hook_handler('forward', '404', 'elgg_error_page_handler'); } + +elgg_register_event_handler('init', 'system', 'page_handler_init'); diff --git a/engine/lib/pageowner.php b/engine/lib/pageowner.php index 6cd4f8e95..bd63d08c6 100644 --- a/engine/lib/pageowner.php +++ b/engine/lib/pageowner.php @@ -13,7 +13,7 @@ * @param int $guid Optional parameter used by elgg_set_page_owner_guid(). * * @return int The current page owner guid (0 if none). - * @since 1.8 + * @since 1.8.0 */ function elgg_get_page_owner_guid($guid = 0) { static $page_owner_guid; @@ -26,100 +26,73 @@ function elgg_get_page_owner_guid($guid = 0) { return $page_owner_guid; } - $guid = trigger_plugin_hook('page_owner', 'system', NULL, 0); + // return guid of page owner entity + $guid = elgg_trigger_plugin_hook('page_owner', 'system', NULL, 0); - $page_owner_guid = $guid; + if ($guid) { + $page_owner_guid = $guid; + } return $guid; } /** - * Gets the guid of the entity that owns the current page. - * - * @deprecated 1.8 Use elgg_get_page_owner_guid() - * - * @return int The current page owner guid (0 if none). - */ -function page_owner() { - elgg_deprecated_notice('page_owner() was deprecated by elgg_get_page_owner_guid().', 1.8); - return elgg_get_page_owner_guid(); -} - -/** * Gets the owner entity for the current page. * - * @return ElggEntity|false The current page owner or false if none. + * @note Access is disabled when getting the page owner entity. * - * @since 1.8 + * @return ElggUser|ElggGroup|false The current page owner or false if none. + * + * @since 1.8.0 */ -function elgg_get_page_owner() { +function elgg_get_page_owner_entity() { $guid = elgg_get_page_owner_guid(); if ($guid > 0) { - return get_entity($guid); - } + $ia = elgg_set_ignore_access(true); + $owner = get_entity($guid); + elgg_set_ignore_access($ia); - return FALSE; -} + return $owner; + } -/** - * Gets the owner entity for the current page. - * - * @deprecated 1.8 Use elgg_get_page_owner() - * @return ElggEntity|false The current page owner or false if none. - * - * @since 1.8 - */ -function page_owner_entity() { - elgg_deprecated_notice('page_owner_entity() was deprecated by elgg_get_page_owner().', 1.8); - return elgg_get_page_owner(); + return false; } /** * Set the guid of the entity that owns this page * * @param int $guid The guid of the page owner - * - * @since 1.8 + * @return void + * @since 1.8.0 */ function elgg_set_page_owner_guid($guid) { elgg_get_page_owner_guid($guid); } - /** - * Registers a page owner handler function + * Sets the page owner based on request * - * @param string $functionname The callback function + * Tries to figure out the page owner by looking at the URL or a request + * parameter. The request parameters used are 'username' and 'owner_guid'. If + * the page request is going through the page handling system, this function + * attempts to figure out the owner if the url fits the patterns of: + * <handler>/owner/<username> + * <handler>/friends/<username> + * <handler>/view/<entity guid> + * <handler>/add/<container guid> + * <handler>/edit/<entity guid> + * <handler>/group/<group guid> * - * @deprecated 1.8 Use the 'page_owner', 'system' plugin hook - * @return void - */ -function add_page_owner_handler($functionname) { - elgg_deprecated_notice("add_page_owner_handler() was deprecated by the plugin hook 'page_owner', 'system'.", 1.8); -} - -/** - * Set a page owner entity + * @note Access is disabled while finding the page owner for the group gatekeeper functions. * - * @param int $entitytoset The GUID of the entity - * - * @deprecated 1.8 Use elgg_set_page_owner_guid() - * @return void - */ -function set_page_owner($entitytoset = -1) { - elgg_deprecated_notice('set_page_owner() was deprecated by elgg_set_page_owner_guid().', 1.8); - elgg_set_page_owner_guid($entitytoset); -} - -/** - * Handles default page owners * - * @param string $hook page_owner - * @param string $entity_type system - * @param mixed $returnvalue Previous function's return value - * @param mixed $params Params + * @param string $hook 'page_owner' + * @param string $entity_type 'system' + * @param int $returnvalue Previous function's return value + * @param array $params no parameters * - * @return int + * @return int GUID + * @access private */ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) { @@ -127,17 +100,22 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) return $returnvalue; } + $ia = elgg_set_ignore_access(true); + $username = get_input("username"); if ($username) { + // @todo using a username of group:<guid> is deprecated if (substr_count($username, 'group:')) { preg_match('/group\:([0-9]+)/i', $username, $matches); $guid = $matches[1]; if ($entity = get_entity($guid)) { + elgg_set_ignore_access($ia); return $entity->getGUID(); } } if ($user = get_user_by_username($username)) { + elgg_set_ignore_access($ia); return $user->getGUID(); } } @@ -145,11 +123,53 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) $owner = get_input("owner_guid"); if ($owner) { if ($user = get_entity($owner)) { + elgg_set_ignore_access($ia); return $user->getGUID(); } } - return $returnvalue; + // ignore root and query + $uri = current_page_url(); + $path = str_replace(elgg_get_site_url(), '', $uri); + $path = trim($path, "/"); + if (strpos($path, "?")) { + $path = substr($path, 0, strpos($path, "?")); + } + + // @todo feels hacky + if (get_input('page', FALSE)) { + $segments = explode('/', $path); + if (isset($segments[1]) && isset($segments[2])) { + switch ($segments[1]) { + case 'owner': + case 'friends': + $user = get_user_by_username($segments[2]); + if ($user) { + elgg_set_ignore_access($ia); + return $user->getGUID(); + } + break; + case 'view': + case 'edit': + $entity = get_entity($segments[2]); + if ($entity) { + elgg_set_ignore_access($ia); + return $entity->getContainerGUID(); + } + break; + case 'add': + case 'group': + $entity = get_entity($segments[2]); + if ($entity) { + elgg_set_ignore_access($ia); + return $entity->getGUID(); + } + break; + } + } + } + + elgg_set_ignore_access($ia); } /** @@ -160,8 +180,8 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) * output could be different for those two contexts ('blog' vs 'widget'). * * Pages that pass through the page handling system set the context to the - * first string after 'pg'. Example: http://elgg.org/pg/bookmarks/ results in - * the initial context being set to 'bookmarks'. + * first string after the root url. Example: http://example.org/elgg/bookmarks/ + * results in the initial context being set to 'bookmarks'. * * The context is a stack so that for a widget on a profile, the context stack * may contain first 'profile' and then 'widget'. @@ -171,9 +191,9 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params) * @warning The context is not available until the page_handler runs (after * the 'init, system' event processing has completed). * - * @param string $context The context of the page + * @param string $context The context of the page * @return bool - * @since 1.8 + * @since 1.8.0 */ function elgg_set_context($context) { global $CONFIG; @@ -198,11 +218,15 @@ function elgg_set_context($context) { * Since context is a stack, this is equivalent to a peek. * * @return string|NULL - * @since 1.8 + * @since 1.8.0 */ function elgg_get_context() { global $CONFIG; + if (!$CONFIG->context) { + return null; + } + return $CONFIG->context[count($CONFIG->context) - 1]; } @@ -210,7 +234,8 @@ function elgg_get_context() { * Push a context onto the top of the stack * * @param string $context The context string to add to the context stack - * @since 1.8 + * @return void + * @since 1.8.0 */ function elgg_push_context($context) { global $CONFIG; @@ -222,7 +247,7 @@ function elgg_push_context($context) { * Removes and returns the top context string from the stack * * @return string|NULL - * @since 1.8 + * @since 1.8.0 */ function elgg_pop_context() { global $CONFIG; @@ -238,9 +263,9 @@ function elgg_pop_context() { * itself differently based on being on the dashboard or profile pages, it * can check the stack. * - * @param string $context The context string to check for + * @param string $context The context string to check for * @return bool - * @since 1.8 + * @since 1.8.0 */ function elgg_in_context($context) { global $CONFIG; @@ -249,53 +274,24 @@ function elgg_in_context($context) { } /** - * Sets the functional context of a page - * - * @deprecated 1.8 Use elgg_set_context() - * - * @param string $context The context of the page - * - * @return mixed Either the context string, or false on failure - */ -function set_context($context) { - elgg_deprecated_notice('set_context() was deprecated by elgg_set_context().', 1.8); - elgg_set_context($context); - if (empty($context)) { - return false; - } - return $context; -} - -/** - * Returns the functional context of a page - * - * @deprecated 1.8 Use elgg_get_context() - * - * @return string The context, or 'main' if no context has been provided - */ -function get_context() { - elgg_deprecated_notice('get_context() was deprecated by elgg_get_context().', 1.8); - return elgg_get_context(); - - // @todo - used to set context based on calling script - // $context = get_plugin_name(true) -} - - -/** * Initializes the page owner functions * * @note This is on the 'boot, system' event so that the context is set up quickly. * * @return void + * @access private */ function page_owner_boot() { - global $CONFIG; - register_plugin_hook('page_owner', 'system', 'default_page_owner_handler'); - - // initial context - will be replaced by page handler - $CONFIG->context = array('main'); + elgg_register_plugin_hook_handler('page_owner', 'system', 'default_page_owner_handler'); + + // Bootstrap the context stack by setting its first entry to the handler. + // This is the first segment of the URL and the handler is set by the rewrite rules. + // @todo this does not work for actions + $handler = get_input('handler', FALSE); + if ($handler) { + elgg_set_context($handler); + } } -register_elgg_event_handler('boot', 'system', 'page_owner_boot');
\ No newline at end of file +elgg_register_event_handler('boot', 'system', 'page_owner_boot'); diff --git a/engine/lib/pam.php b/engine/lib/pam.php index 21cfdbbb9..1c9c3bfe1 100644 --- a/engine/lib/pam.php +++ b/engine/lib/pam.php @@ -14,22 +14,30 @@ * For more information on PAMs see: * http://www.freebsd.org/doc/en/articles/pam/index.html * + * @see ElggPAM + * * @package Elgg.Core * @subpackage Authentication.PAM */ +global $_PAM_HANDLERS; $_PAM_HANDLERS = array(); -$_PAM_HANDLERS_MSG = array(); /** * Register a PAM handler. * - * @param string $handler The handler function in the format + * A PAM handler should return true if the authentication attempt passed. For a + * failure, return false or throw an exception. Returning nothing indicates that + * the handler wants to be skipped. + * + * Note, $handler must be string callback (not an array/Closure). + * + * @param string $handler Callable global handler function in the format () * pam_handler($credentials = NULL); * @param string $importance The importance - "sufficient" (default) or "required" * @param string $policy The policy type, default is "user" * - * @return boolean + * @return bool */ function register_pam_handler($handler, $importance = "sufficient", $policy = "user") { global $_PAM_HANDLERS; @@ -39,7 +47,8 @@ function register_pam_handler($handler, $importance = "sufficient", $policy = "u $_PAM_HANDLERS[$policy] = array(); } - if (is_callable($handler)) { + // @todo remove requirement that $handle be a global function + if (is_string($handler) && is_callable($handler, true)) { $_PAM_HANDLERS[$policy][$handler] = new stdClass; $_PAM_HANDLERS[$policy][$handler]->handler = $handler; @@ -65,60 +74,3 @@ function unregister_pam_handler($handler, $policy = "user") { unset($_PAM_HANDLERS[$policy][$handler]); } - -/** - * Attempt to authenticate. - * This function will process all registered PAM handlers or stop when the first - * handler fails. A handler fails by either returning false or throwing an - * exception. The advantage of throwing an exception is that it returns a message - * through the global $_PAM_HANDLERS_MSG which can be used in communication with - * a user. The order that handlers are processed is determined by the order that - * they were registered. - * - * If $credentials are provided the PAM handler should authenticate using the - * provided credentials, if not then credentials should be prompted for or - * otherwise retrieved (eg from the HTTP header or $_SESSION). - * - * @param mixed $credentials Mixed PAM handler specific credentials (e.g. username, password) - * @param string $policy The policy type, default is "user" - * - * @return bool true if authenticated, false if not. - */ -function pam_authenticate($credentials = NULL, $policy = "user") { - global $_PAM_HANDLERS, $_PAM_HANDLERS_MSG; - - $_PAM_HANDLERS_MSG = array(); - - $authenticated = false; - - foreach ($_PAM_HANDLERS[$policy] as $k => $v) { - $handler = $v->handler; - $importance = $v->importance; - - try { - // Execute the handler - if ($handler($credentials)) { - // Explicitly returned true - $_PAM_HANDLERS_MSG[$k] = "Authenticated!"; - - $authenticated = true; - } else { - $_PAM_HANDLERS_MSG[$k] = "Not Authenticated."; - - // If this is required then abort. - if ($importance == 'required') { - return false; - } - } - } catch (Exception $e) { - $_PAM_HANDLERS_MSG[$k] = "$e"; - - // If this is required then abort. - if ($importance == 'required') { - return false; - } - } - } - - return $authenticated; -}
\ No newline at end of file diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php index d6352cbc6..d5d3db466 100644 --- a/engine/lib/plugins.php +++ b/engine/lib/plugins.php @@ -7,220 +7,538 @@ * @subpackage Plugins */ -/// Cache enabled plugins per page -$ENABLED_PLUGINS_CACHE = NULL; +/** + * Tells ElggPlugin::start() to include the start.php file. + */ +define('ELGG_PLUGIN_INCLUDE_START', 1); + +/** + * Tells ElggPlugin::start() to automatically register the plugin's views. + */ +define('ELGG_PLUGIN_REGISTER_VIEWS', 2); + +/** + * Tells ElggPlugin::start() to automatically register the plugin's languages. + */ +define('ELGG_PLUGIN_REGISTER_LANGUAGES', 4); + +/** + * Tells ElggPlugin::start() to automatically register the plugin's classes. + */ +define('ELGG_PLUGIN_REGISTER_CLASSES', 8); /** - * Returns a list of plugins to load, in the order that they should be loaded. + * Prefix for plugin setting names * - * @return array List of plugins + * @todo Can't namespace these because many plugins directly call + * private settings via $entity->$name. */ -function get_plugin_list() { - global $CONFIG; +//define('ELGG_PLUGIN_SETTING_PREFIX', 'plugin:setting:'); - if (!empty($CONFIG->pluginlistcache)) { - return $CONFIG->pluginlistcache; +/** + * Prefix for plugin user setting names + */ +define('ELGG_PLUGIN_USER_SETTING_PREFIX', 'plugin:user_setting:'); + +/** + * Internal settings prefix + * + * @todo This could be resolved by promoting ElggPlugin to a 5th type. + */ +define('ELGG_PLUGIN_INTERNAL_PREFIX', 'elgg:internal:'); + + +/** + * Returns a list of plugin IDs (dir names) from a dir. + * + * @param string $dir A dir to scan for plugins. Defaults to config's plugins_path. + * + * @return array + * @since 1.8.0 + * @access private + */ +function elgg_get_plugin_ids_in_dir($dir = null) { + if (!$dir) { + $dir = elgg_get_plugins_path(); } - if ($site = get_entity($CONFIG->site_guid)) { - $pluginorder = $site->pluginorder; - if (!empty($pluginorder)) { - $plugins = unserialize($pluginorder); + $plugin_ids = array(); + $handle = opendir($dir); - $CONFIG->pluginlistcache = $plugins; - return $plugins; - } else { - // this only runs on install, otherwise uses serialized plugin order - $plugins = array(); - - if ($handle = opendir($CONFIG->pluginspath)) { - while ($mod = readdir($handle)) { - // must be directory and not begin with a . - if (substr($mod, 0, 1) !== '.' && is_dir($CONFIG->pluginspath . "/" . $mod)) { - $plugins[] = $mod; - } - } + if ($handle) { + while ($plugin_id = readdir($handle)) { + // must be directory and not begin with a . + if (substr($plugin_id, 0, 1) !== '.' && is_dir($dir . $plugin_id)) { + $plugin_ids[] = $plugin_id; + } + } + } + + sort($plugin_ids); + + return $plugin_ids; +} + +/** + * Discovers plugins in the plugins_path setting and creates ElggPlugin + * entities for them if they don't exist. If there are plugins with entities + * but not actual files, will disable the ElggPlugin entities and mark as inactive. + * The ElggPlugin object holds config data, so don't delete. + * + * @todo Crappy name? + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_generate_plugin_entities() { + // @todo $site unused, can remove? + $site = get_config('site'); + + $dir = elgg_get_plugins_path(); + $db_prefix = elgg_get_config('dbprefix'); + + $options = array( + 'type' => 'object', + 'subtype' => 'plugin', + 'selects' => array('plugin_oe.*'), + 'joins' => array("JOIN {$db_prefix}objects_entity plugin_oe on plugin_oe.guid = e.guid"), + 'limit' => ELGG_ENTITIES_NO_VALUE + ); + + $old_ia = elgg_set_ignore_access(true); + $old_access = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $known_plugins = elgg_get_entities_from_relationship($options); + /* @var ElggPlugin[] $known_plugins */ + + if (!$known_plugins) { + $known_plugins = array(); + } + + // map paths to indexes + $id_map = array(); + foreach ($known_plugins as $i => $plugin) { + // if the ID is wrong, delete the plugin because we can never load it. + $id = $plugin->getID(); + if (!$id) { + $plugin->delete(); + unset($known_plugins[$i]); + continue; + } + $id_map[$plugin->getID()] = $i; + } + + $physical_plugins = elgg_get_plugin_ids_in_dir($dir); + + if (!$physical_plugins) { + return false; + } + + // check real plugins against known ones + foreach ($physical_plugins as $plugin_id) { + // is this already in the db? + if (array_key_exists($plugin_id, $id_map)) { + $index = $id_map[$plugin_id]; + $plugin = $known_plugins[$index]; + // was this plugin deleted and its entity disabled? + if (!$plugin->isEnabled()) { + $plugin->enable(); + $plugin->deactivate(); + $plugin->setPriority('last'); } - sort($plugins); + // remove from the list of plugins to disable + unset($known_plugins[$index]); + } else { + // add new plugins + // priority is force to last in save() if not set. + $plugin = new ElggPlugin($plugin_id); + $plugin->save(); + } + } - $CONFIG->pluginlistcache = $plugins; - return $plugins; + // everything remaining in $known_plugins needs to be disabled + // because they are entities, but their dirs were removed. + // don't delete the entities because they hold settings. + foreach ($known_plugins as $plugin) { + if ($plugin->isActive()) { + $plugin->deactivate(); } + // remove the priority. + $name = elgg_namespace_plugin_private_setting('internal', 'priority'); + remove_private_setting($plugin->guid, $name); + $plugin->disable(); + } + + access_show_hidden_entities($old_access); + elgg_set_ignore_access($old_ia); + + elgg_reindex_plugin_priorities(); + + return true; +} + +/** + * Cache a reference to this plugin by its ID + * + * @param ElggPlugin $plugin + * + * @access private + */ +function _elgg_cache_plugin_by_id(ElggPlugin $plugin) { + $map = (array) elgg_get_config('plugins_by_id_map'); + $map[$plugin->getID()] = $plugin; + elgg_set_config('plugins_by_id_map', $map); +} + +/** + * Returns an ElggPlugin object with the path $path. + * + * @param string $plugin_id The id (dir name) of the plugin. NOT the guid. + * @return ElggPlugin|false + * @since 1.8.0 + */ +function elgg_get_plugin_from_id($plugin_id) { + $map = (array) elgg_get_config('plugins_by_id_map'); + if (isset($map[$plugin_id])) { + return $map[$plugin_id]; + } + + $plugin_id = sanitize_string($plugin_id); + $db_prefix = get_config('dbprefix'); + + $options = array( + 'type' => 'object', + 'subtype' => 'plugin', + 'joins' => array("JOIN {$db_prefix}objects_entity oe on oe.guid = e.guid"), + 'selects' => array("oe.title", "oe.description"), + 'wheres' => array("oe.title = '$plugin_id'"), + 'limit' => 1 + ); + + $plugins = elgg_get_entities($options); + + if ($plugins) { + return $plugins[0]; } return false; } /** - * Regenerates the list of known plugins and saves it to the current site + * Returns if a plugin exists in the system. * - * Important: You should regenerate simplecache and the viewpath cache after executing this function - * otherwise you may experience view display artifacts. Do this with the following code: + * @warning This checks only plugins that are registered in the system! + * If the plugin cache is outdated, be sure to regenerate it with + * {@link elgg_generate_plugin_objects()} first. * - * elgg_view_regenerate_simplecache(); - * elgg_filepath_cache_reset(); + * @param string $id The plugin ID. + * @since 1.8.0 + * @return bool + */ +function elgg_plugin_exists($id) { + $plugin = elgg_get_plugin_from_id($id); + + return ($plugin) ? true : false; +} + +/** + * Returns the highest priority of the plugins * - * @param array $pluginorder Optionally, a list of existing plugins and their orders + * @return int + * @since 1.8.0 + * @access private + */ +function elgg_get_max_plugin_priority() { + $db_prefix = get_config('dbprefix'); + $priority = elgg_namespace_plugin_private_setting('internal', 'priority'); + $plugin_subtype = get_subtype_id('object', 'plugin'); + + $q = "SELECT MAX(CAST(ps.value AS unsigned)) as max + FROM {$db_prefix}entities e, {$db_prefix}private_settings ps + WHERE ps.name = '$priority' + AND ps.entity_guid = e.guid + AND e.type = 'object' and e.subtype = $plugin_subtype"; + + $data = get_data($q); + if ($data) { + $max = $data[0]->max; + } else { + $max = 1; + } + + // can't have a priority of 0. + return ($max) ? $max : 1; +} + +/** + * Returns if a plugin is active for a current site. * - * @return array The new list of plugins and their orders + * @param string $plugin_id The plugin ID + * @param int $site_guid The site guid + * @since 1.8.0 + * @return bool */ -function regenerate_plugin_list($pluginorder = FALSE) { - global $CONFIG; +function elgg_is_active_plugin($plugin_id, $site_guid = null) { + if ($site_guid) { + $site = get_entity($site_guid); + } else { + $site = elgg_get_site_entity(); + } + + if (!($site instanceof ElggSite)) { + return false; + } - $CONFIG->pluginlistcache = NULL; + $plugin = elgg_get_plugin_from_id($plugin_id); - if ($site = get_entity($CONFIG->site_guid)) { - if (empty($pluginorder)) { - $pluginorder = $site->pluginorder; - $pluginorder = unserialize($pluginorder); - } else { - ksort($pluginorder); - } + if (!$plugin) { + return false; + } - if (empty($pluginorder)) { - $pluginorder = array(); - } + return $plugin->isActive($site->guid); +} - $max = 0; - if (sizeof($pluginorder)) { - foreach ($pluginorder as $key => $plugin) { - if (is_dir($CONFIG->pluginspath . "/" . $plugin)) { - if ($key > $max) { - $max = $key; - } - } else { - unset($pluginorder[$key]); - } - } - } - // Add new plugins to the end - if ($handle = opendir($CONFIG->pluginspath)) { - while ($mod = readdir($handle)) { - // must be directory and not begin with a . - if (substr($mod, 0, 1) !== '.' && is_dir($CONFIG->pluginspath . "/" . $mod)) { - if (!in_array($mod, $pluginorder)) { - $max = $max + 10; - $pluginorder[$max] = $mod; - } - } - } +/** + * Loads all active plugins in the order specified in the tool admin panel. + * + * @note This is called on every page load. If a plugin is active and problematic, it + * will be disabled and a visible error emitted. This does not check the deps system because + * that was too slow. + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_load_plugins() { + $plugins_path = elgg_get_plugins_path(); + $start_flags = ELGG_PLUGIN_INCLUDE_START | + ELGG_PLUGIN_REGISTER_VIEWS | + ELGG_PLUGIN_REGISTER_LANGUAGES | + ELGG_PLUGIN_REGISTER_CLASSES; + + if (!$plugins_path) { + return false; + } + + // temporary disable all plugins if there is a file called 'disabled' in the plugin dir + if (file_exists("$plugins_path/disabled")) { + if (elgg_is_admin_logged_in() && elgg_in_context('admin')) { + system_message(elgg_echo('plugins:disabled')); } + return false; + } + + if (elgg_get_config('system_cache_loaded')) { + $start_flags = $start_flags & ~ELGG_PLUGIN_REGISTER_VIEWS; + } - ksort($pluginorder); + if (elgg_get_config('i18n_loaded_from_cache')) { + $start_flags = $start_flags & ~ELGG_PLUGIN_REGISTER_LANGUAGES; + } - // Now reorder the keys .. - $key = 10; - $plugins = array(); - if (sizeof($pluginorder)) { - foreach ($pluginorder as $plugin) { - $plugins[$key] = $plugin; - $key = $key + 10; + $return = true; + $plugins = elgg_get_plugins('active'); + if ($plugins) { + foreach ($plugins as $plugin) { + try { + $plugin->start($start_flags); + } catch (Exception $e) { + $plugin->deactivate(); + $msg = elgg_echo('PluginException:CannotStart', + array($plugin->getID(), $plugin->guid, $e->getMessage())); + elgg_add_admin_notice('cannot_start' . $plugin->getID(), $msg); + $return = false; + + continue; } } + } + + return $return; +} - $plugins = serialize($plugins); +/** + * Returns an ordered list of plugins + * + * @param string $status The status of the plugins. active, inactive, or all. + * @param mixed $site_guid Optional site guid + * @return ElggPlugin[] + * @since 1.8.0 + * @access private + */ +function elgg_get_plugins($status = 'active', $site_guid = null) { + $db_prefix = get_config('dbprefix'); + $priority = elgg_namespace_plugin_private_setting('internal', 'priority'); - $site->pluginorder = $plugins; + if (!$site_guid) { + $site = get_config('site'); + $site_guid = $site->guid; + } - return $plugins; + // grab plugins + $options = array( + 'type' => 'object', + 'subtype' => 'plugin', + 'limit' => ELGG_ENTITIES_NO_VALUE, + 'selects' => array('plugin_oe.*'), + 'joins' => array( + "JOIN {$db_prefix}private_settings ps on ps.entity_guid = e.guid", + "JOIN {$db_prefix}objects_entity plugin_oe on plugin_oe.guid = e.guid" + ), + 'wheres' => array("ps.name = '$priority'"), + 'order_by' => "CAST(ps.value as unsigned), e.guid" + ); + + switch ($status) { + case 'active': + $options['relationship'] = 'active_plugin'; + $options['relationship_guid'] = $site_guid; + $options['inverse_relationship'] = true; + break; + + case 'inactive': + $options['wheres'][] = "NOT EXISTS ( + SELECT 1 FROM {$db_prefix}entity_relationships active_er + WHERE active_er.guid_one = e.guid + AND active_er.relationship = 'active_plugin' + AND active_er.guid_two = $site_guid)"; + break; + + case 'all': + default: + break; } - return FALSE; -} + $old_ia = elgg_set_ignore_access(true); + $plugins = elgg_get_entities_from_relationship($options); + elgg_set_ignore_access($old_ia); + return $plugins; +} /** - * For now, loads plugins directly + * Reorder plugins to an order specified by the array. + * Plugins not included in this array will be appended to the end. * - * @todo Add proper plugin handler that launches plugins in an - * admin-defined order and activates them on admin request + * @note This doesn't use the ElggPlugin->setPriority() method because + * all plugins are being changed and we don't want it to automatically + * reorder plugins. * - * @return void + * @param array $order An array of plugin ids in the order to set them + * @return bool + * @since 1.8.0 + * @access private */ -function load_plugins() { - global $CONFIG; +function elgg_set_plugin_priorities(array $order) { + $name = elgg_namespace_plugin_private_setting('internal', 'priority'); + + $plugins = elgg_get_plugins('any'); + if (!$plugins) { + return false; + } + + $return = true; - if (!empty($CONFIG->pluginspath)) { - // See if we have cached values for things - $cached_view_paths = elgg_filepath_cache_load(); - if ($cached_view_paths) { - $CONFIG->views = unserialize($cached_view_paths); + // reindex to get standard counting. no need to increment by 10. + // though we do start with 1 + $order = array_values($order); + + $missing_plugins = array(); + foreach ($plugins as $plugin) { + $plugin_id = $plugin->getID(); + + if (!in_array($plugin_id, $order)) { + $missing_plugins[] = $plugin; + continue; } - // temporary disable all plugins if there is a file called 'disabled' in the plugin dir - if (file_exists($CONFIG->pluginspath . "disabled")) { - return; + $priority = array_search($plugin_id, $order) + 1; + + if (!$plugin->set($name, $priority)) { + $return = false; + break; } + } - $plugins = get_plugin_list(); - - if (sizeof($plugins)) { - foreach ($plugins as $mod) { - if (is_plugin_enabled($mod)) { - if (file_exists($CONFIG->pluginspath . $mod)) { - if (!include($CONFIG->pluginspath . $mod . "/start.php")) { - // automatically disable the bad plugin - disable_plugin($mod); - - // register error rather than rendering the site unusable with exception - register_error(sprintf(elgg_echo('PluginException:MisconfiguredPlugin'), $mod)); - - // continue loading remaining plugins - continue; - } - - if (!$cached_view_paths) { - $view_dir = $CONFIG->pluginspath . $mod . '/views/'; - - if (is_dir($view_dir) && ($handle = opendir($view_dir))) { - while (FALSE !== ($view_type = readdir($handle))) { - $view_type_dir = $view_dir . $view_type; - - if ('.' !== substr($view_type, 0, 1) && is_dir($view_type_dir)) { - if (autoregister_views('', $view_type_dir, $view_dir, $view_type)) { - // add the valid view type. - if (!in_array($view_type, $CONFIG->view_types)) { - $CONFIG->view_types[] = $view_type; - } - } - } - } - } - } - - if (is_dir($CONFIG->pluginspath . $mod . "/languages")) { - register_translations($CONFIG->pluginspath . $mod . "/languages/"); - } - - if (is_dir($CONFIG->pluginspath . "$mod/classes")) { - elgg_register_classes($CONFIG->pluginspath . "$mod/classes"); - } - } - } + // set the missing plugins' priorities + if ($return && $missing_plugins) { + if (!isset($priority)) { + $priority = 0; + } + foreach ($missing_plugins as $plugin) { + $priority++; + if (!$plugin->set($name, $priority)) { + $return = false; + break; } } + } - // Cache results - if (!$cached_view_paths) { - elgg_filepath_cache_save(serialize($CONFIG->views)); - } + return $return; +} + +/** + * Reindexes all plugin priorities starting at 1. + * + * @todo Can this be done in a single sql command? + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_reindex_plugin_priorities() { + return elgg_set_plugin_priorities(array()); +} + +/** + * Namespaces a string to be used as a private setting for a plugin. + * + * @param string $type The type of value: user_setting or internal. + * @param string $name The name to namespace. + * @param string $id The plugin's ID to namespace with. Required for user_setting. + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_namespace_plugin_private_setting($type, $name, $id = null) { + switch ($type) { + // commented out because it breaks $plugin->$name access to variables + //case 'setting': + // $name = ELGG_PLUGIN_SETTING_PREFIX . $name; + // break; + + case 'user_setting': + if (!$id) { + $id = elgg_get_calling_plugin_id(); + } + $name = ELGG_PLUGIN_USER_SETTING_PREFIX . "$id:$name"; + break; + + case 'internal': + $name = ELGG_PLUGIN_INTERNAL_PREFIX . $name; + break; } + + return $name; } /** * Get the name of the most recent plugin to be called in the * call stack (or the plugin that owns the current page, if any). * - * i.e., if the last plugin was in /mod/foobar/, get_plugin_name would return foo_bar. + * i.e., if the last plugin was in /mod/foobar/, this would return foo_bar. * * @param boolean $mainfilename If set to true, this will instead determine the * context from the main script filename called by * the browser. Default = false. * * @return string|false Plugin name, or false if no plugin name was called + * @since 1.8.0 + * @access private + * + * @todo get rid of this */ -function get_plugin_name($mainfilename = false) { +function elgg_get_calling_plugin_id($mainfilename = false) { if (!$mainfilename) { if ($backtrace = debug_backtrace()) { foreach ($backtrace as $step) { @@ -233,8 +551,9 @@ function get_plugin_name($mainfilename = false) { } } } else { - if (preg_match("/pg\/([a-zA-Z0-9\-\_]*)\//", $_SERVER['REQUEST_URI'], $matches)) { - return $matches[1]; + //@todo this is a hack -- plugins do not have to match their page handler names! + if ($handler = get_input('handler', FALSE)) { + return $handler; } else { $file = $_SERVER["SCRIPT_NAME"]; $file = str_replace("\\", "/", $file); @@ -248,635 +567,613 @@ function get_plugin_name($mainfilename = false) { } /** - * Load and parse a plugin manifest from a plugin XML file. - * - * Example file: - * - * <plugin_manifest> - * <!-- Basic information --> - * <field key="name" value="My Plugin" /> - * <field key="description" value="My Plugin's concise description" /> - * <field key="version" value="1.0" /> - * <field key="category" value="theme" /> - * <field key="category" value="bundled" /> - * <field key="screenshot" value="path/relative/to/my_plugin.jpg" /> - * <field key="screenshot" value="path/relative/to/my_plugin_2.jpg" /> + * Returns an array of all provides from all active plugins. * - * <field key="author" value="Curverider Ltd" /> - * <field key="website" value="http://www.elgg.org/" /> - * <field key="copyright" value="(C) Curverider 2008-2010" /> - * <field key="licence" value="GNU Public License version 2" /> - * </plugin_manifest> + * Array in the form array( + * 'provide_type' => array( + * 'provided_name' => array( + * 'version' => '1.8', + * 'provided_by' => 'provider_plugin_id' + * ) + * ) + * ) * - * @param string $plugin Plugin name. + * @param string $type The type of provides to return + * @param string $name A specific provided name to return. Requires $provide_type. * - * @return array of values + * @return array + * @since 1.8.0 + * @access private */ -function load_plugin_manifest($plugin) { - global $CONFIG; - - $xml = xml_to_object(file_get_contents($CONFIG->pluginspath . $plugin . "/manifest.xml")); - - if ($xml) { - // set up some defaults to normalize expected values to arrays - $elements = array( - 'screenshot' => array(), - 'category' => array() - ); - - foreach ($xml->children as $element) { - $key = $element->attributes['key']; - $value = $element->attributes['value']; - - // create arrays if multiple fields are set - if (array_key_exists($key, $elements)) { - if (!is_array($elements[$key])) { - $orig = $elements[$key]; - $elements[$key] = array($orig); +function elgg_get_plugins_provides($type = null, $name = null) { + static $provides = null; + $active_plugins = elgg_get_plugins('active'); + + if (!isset($provides)) { + $provides = array(); + + foreach ($active_plugins as $plugin) { + $plugin_provides = array(); + $manifest = $plugin->getManifest(); + if ($manifest instanceof ElggPluginManifest) { + $plugin_provides = $plugin->getManifest()->getProvides(); + } + if ($plugin_provides) { + foreach ($plugin_provides as $provided) { + $provides[$provided['type']][$provided['name']] = array( + 'version' => $provided['version'], + 'provided_by' => $plugin->getID() + ); } - - $elements[$key][] = $value; - } else { - $elements[$key] = $value; } } + } - // handle plugins that don't define a name - if (!isset($elements['name'])) { - $elements['name'] = ucwords($plugin); + if ($type && $name) { + if (isset($provides[$type][$name])) { + return $provides[$type][$name]; + } else { + return false; + } + } elseif ($type) { + if (isset($provides[$type])) { + return $provides[$type]; + } else { + return false; } - - return $elements; } - return false; + return $provides; } /** - * This function checks a plugin manifest 'elgg_version' value against the current install - * returning TRUE if the elgg_version is >= the current install's version. + * Checks if a plugin is currently providing $type and $name, and optionally + * checking a version. * - * @param string $manifest_elgg_version_string The build version (eg 2009010201). + * @param string $type The type of the provide + * @param string $name The name of the provide + * @param string $version A version to check against + * @param string $comparison The comparison operator to use in version_compare() * - * @return bool + * @return array An array in the form array( + * 'status' => bool Does the provide exist?, + * 'value' => string The version provided + * ) + * @since 1.8.0 + * @access private */ -function check_plugin_compatibility($manifest_elgg_version_string) { - $version = get_version(); - - if (strpos($manifest_elgg_version_string, '.') === false) { - // Using version - $req_version = (int)$manifest_elgg_version_string; +function elgg_check_plugins_provides($type, $name, $version = null, $comparison = 'ge') { + $provided = elgg_get_plugins_provides($type, $name); + if (!$provided) { + return array( + 'status' => false, + 'version' => '' + ); + } - return ($version >= $req_version); + if ($version) { + $status = version_compare($provided['version'], $version, $comparison); + } else { + $status = true; } - return false; + return array( + 'status' => $status, + 'value' => $provided['version'] + ); } /** - * Shorthand function for finding the plugin settings. + * Returns an array of parsed strings for a dependency in the + * format: array( + * 'type' => requires, conflicts, or provides. + * 'name' => The name of the requirement / conflict + * 'value' => A string representing the expected value: <1, >=3, !=enabled + * 'local_value' => The current value, ("Not installed") + * 'comment' => Free form text to help resovle the problem ("Enable / Search for plugin <link>") + * ) * - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. - * - * @return mixed + * @param array $dep An ElggPluginPackage dependency array + * @return array + * @since 1.8.0 + * @access private */ -function find_plugin_settings($plugin_name = "") { - $options = array('type' => 'object', 'subtype' => 'plugin', 'limit' => 9999); - $plugins = elgg_get_entities($options); - $plugin_name = sanitise_string($plugin_name); - if (!$plugin_name) { - $plugin_name = get_plugin_name(); +function elgg_get_plugin_dependency_strings($dep) { + $dep_system = elgg_extract('type', $dep); + $info = elgg_extract('dep', $dep); + $type = elgg_extract('type', $info); + + if (!$dep_system || !$info || !$type) { + return false; } - if ($plugins) { - foreach ($plugins as $plugin) { - if (strcmp($plugin->title, $plugin_name) == 0) { - return $plugin; + // rewrite some of these to be more readable + switch($info['comparison']) { + case 'lt': + $comparison = '<'; + break; + case 'gt': + $comparison = '>'; + break; + case 'ge': + $comparison = '>='; + break; + case 'le': + $comparison = '<='; + break; + default; + $comparison = $info['comparison']; + break; + } + + /* + 'requires' 'plugin oauth_lib' <1.3 1.3 'downgrade' + 'requires' 'php setting bob' >3 3 'change it' + 'conflicts' 'php setting' >3 4 'change it' + 'conflicted''plugin profile' any 1.8 'disable profile' + 'provides' 'plugin oauth_lib' 1.3 -- -- + 'priority' 'before blog' -- after 'move it' + */ + $strings = array(); + $strings['type'] = elgg_echo('ElggPlugin:Dependencies:' . ucwords($dep_system)); + + switch ($type) { + case 'elgg_version': + case 'elgg_release': + // 'Elgg Version' + $strings['name'] = elgg_echo('ElggPlugin:Dependencies:Elgg'); + $strings['expected_value'] = "$comparison {$info['version']}"; + $strings['local_value'] = $dep['value']; + $strings['comment'] = ''; + break; + + case 'php_extension': + // PHP Extension %s [version] + $strings['name'] = elgg_echo('ElggPlugin:Dependencies:PhpExtension', array($info['name'])); + if ($info['version']) { + $strings['expected_value'] = "$comparison {$info['version']}"; + $strings['local_value'] = $dep['value']; + } else { + $strings['expected_value'] = ''; + $strings['local_value'] = ''; } + $strings['comment'] = ''; + break; + + case 'php_ini': + $strings['name'] = elgg_echo('ElggPlugin:Dependencies:PhpIni', array($info['name'])); + $strings['expected_value'] = "$comparison {$info['value']}"; + $strings['local_value'] = $dep['value']; + $strings['comment'] = ''; + break; + + case 'plugin': + $strings['name'] = elgg_echo('ElggPlugin:Dependencies:Plugin', array($info['name'])); + $expected = $info['version'] ? "$comparison {$info['version']}" : elgg_echo('any'); + $strings['expected_value'] = $expected; + $strings['local_value'] = $dep['value'] ? $dep['value'] : '--'; + $strings['comment'] = ''; + break; + + case 'priority': + $expected_priority = ucwords($info['priority']); + $real_priority = ucwords($dep['value']); + $strings['name'] = elgg_echo('ElggPlugin:Dependencies:Priority'); + $strings['expected_value'] = elgg_echo("ElggPlugin:Dependencies:Priority:$expected_priority", array($info['plugin'])); + $strings['local_value'] = elgg_echo("ElggPlugin:Dependencies:Priority:$real_priority", array($info['plugin'])); + $strings['comment'] = ''; + break; + } + + if ($dep['type'] == 'suggests') { + if ($dep['status']) { + $strings['comment'] = elgg_echo('ok'); + } else { + $strings['comment'] = elgg_echo('ElggPlugin:Dependencies:Suggests:Unsatisfied'); + } + } else { + if ($dep['status']) { + $strings['comment'] = elgg_echo('ok'); + } else { + $strings['comment'] = elgg_echo('error'); } } - return false; + return $strings; } /** - * Find the plugin settings for a user. - * - * @param string $plugin_name Plugin name. - * @param int $user_guid The guid who's settings to retrieve. + * Returns the ElggPlugin entity of the last plugin called. * - * @return array of settings in an associative array minus prefix. + * @return mixed ElggPlugin or false + * @since 1.8.0 + * @access private */ -function find_plugin_usersettings($plugin_name = "", $user_guid = 0) { - $plugin_name = sanitise_string($plugin_name); - $user_guid = (int)$user_guid; +function elgg_get_calling_plugin_entity() { + $plugin_id = elgg_get_calling_plugin_id(); - if (!$plugin_name) { - $plugin_name = get_plugin_name(); + if ($plugin_id) { + return elgg_get_plugin_from_id($plugin_id); } - if ($user_guid == 0) { - $user_guid = get_loggedin_userid(); + return false; +} + +/** + * Returns an array of all plugin settings for a user. + * + * @param mixed $user_guid The user GUID or null for the currently logged in user. + * @param string $plugin_id The plugin ID + * @param bool $return_obj Return settings as an object? This can be used to in reusable + * views where the settings are passed as $vars['entity']. + * @return array + * @since 1.8.0 + */ +function elgg_get_all_plugin_user_settings($user_guid = null, $plugin_id = null, $return_obj = false) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - // Get metadata for user - $all_metadata = get_all_private_settings($user_guid); //get_metadata_for_entity($user_guid); - if ($all_metadata) { - $prefix = "plugin:settings:$plugin_name:"; - $return = new stdClass; + if (!$plugin instanceof ElggPlugin) { + return false; + } - foreach ($all_metadata as $key => $meta) { - $name = substr($key, strlen($prefix)); - $value = $meta; + $settings = $plugin->getAllUserSettings($user_guid); - if (strpos($key, $prefix) === 0) { - $return->$name = $value; - } + if ($settings && $return_obj) { + $return = new stdClass; + + foreach ($settings as $k => $v) { + $return->$k = $v; } return $return; + } else { + return $settings; } - - return false; } /** * Set a user specific setting for a plugin. * - * @param string $name The name - note, can't be "title". - * @param mixed $value The value. - * @param int $user_guid Optional user. - * @param string $plugin_name Optional plugin name, if not specified then it - * is detected from where you are calling from. + * @param string $name The name - note, can't be "title". + * @param mixed $value The value. + * @param int $user_guid Optional user. + * @param string $plugin_id Optional plugin name, if not specified then it + * is detected from where you are calling from. * * @return bool + * @since 1.8.0 */ -function set_plugin_usersetting($name, $value, $user_guid = 0, $plugin_name = "") { - $plugin_name = sanitise_string($plugin_name); - $user_guid = (int)$user_guid; - $name = sanitise_string($name); - - if (!$plugin_name) { - $plugin_name = get_plugin_name(); - } - - $user = get_entity($user_guid); - if (!$user) { - $user = get_loggedin_user(); +function elgg_set_plugin_user_setting($name, $value, $user_guid = null, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - if (($user) && ($user instanceof ElggUser)) { - $prefix = "plugin:settings:$plugin_name:$name"; - //$user->$prefix = $value; - //$user->save(); - - // Hook to validate setting - $value = trigger_plugin_hook('plugin:usersetting', 'user', array( - 'user' => $user, - 'plugin' => $plugin_name, - 'name' => $name, - 'value' => $value - ), $value); - - return set_private_setting($user->guid, $prefix, $value); + if (!$plugin) { + return false; } - return false; + return $plugin->setUserSetting($name, $value, $user_guid); } /** - * Clears a user-specific plugin setting + * Unsets a user-specific plugin setting * - * @param str $name Name of the plugin setting - * @param int $user_guid Defaults to logged in user - * @param str $plugin_name Defaults to contextual plugin name + * @param string $name Name of the setting + * @param int $user_guid Defaults to logged in user + * @param string $plugin_id Defaults to contextual plugin name * - * @return bool Success + * @return bool + * @since 1.8.0 */ -function clear_plugin_usersetting($name, $user_guid = 0, $plugin_name = '') { - $plugin_name = sanitise_string($plugin_name); - $name = sanitise_string($name); - - if (!$plugin_name) { - $plugin_name = get_plugin_name(); - } - - $user = get_entity((int) $user_guid); - if (!$user) { - $user = get_loggedin_user(); +function elgg_unset_plugin_user_setting($name, $user_guid = null, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - if (($user) && ($user instanceof ElggUser)) { - $prefix = "plugin:settings:$plugin_name:$name"; - - return remove_private_setting($user->getGUID(), $prefix); + if (!$plugin) { + return false; } - return FALSE; + return $plugin->unsetUserSetting($name, $user_guid); } /** * Get a user specific setting for a plugin. * - * @param string $name The name. - * @param int $user_guid Guid of owning user - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. + * @param string $name The name of the setting. + * @param int $user_guid Guid of owning user + * @param string $plugin_id Optional plugin name, if not specified + * it is detected from where you are calling. * * @return mixed + * @since 1.8.0 */ -function get_plugin_usersetting($name, $user_guid = 0, $plugin_name = "") { - $plugin_name = sanitise_string($plugin_name); - $user_guid = (int)$user_guid; - $name = sanitise_string($name); - - if (!$plugin_name) { - $plugin_name = get_plugin_name(); - } - - $user = get_entity($user_guid); - if (!$user) { - $user = get_loggedin_user(); +function elgg_get_plugin_user_setting($name, $user_guid = null, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - if (($user) && ($user instanceof ElggUser)) { - $prefix = "plugin:settings:$plugin_name:$name"; - return get_private_setting($user->guid, $prefix); + if (!$plugin) { + return false; } - return false; + return $plugin->getUserSetting($name, $user_guid); } /** * Set a setting for a plugin. * - * @param string $name The name - note, can't be "title". - * @param mixed $value The value. - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. + * @param string $name The name of the setting - note, can't be "title". + * @param mixed $value The value. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. * - * @return int|false + * @return bool + * @since 1.8.0 */ -function set_plugin_setting($name, $value, $plugin_name = "") { - if (!$plugin_name) { - $plugin_name = get_plugin_name(); +function elgg_set_plugin_setting($name, $value, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - $plugin = find_plugin_settings($plugin_name); if (!$plugin) { - $plugin = new ElggPlugin(); - } - - if ($name != 'title') { - // Hook to validate setting - $value = trigger_plugin_hook('plugin:setting', 'plugin', array( - 'plugin' => $plugin_name, - 'name' => $name, - 'value' => $value - ), $value); - - $plugin->title = $plugin_name; - $plugin->access_id = ACCESS_PUBLIC; - $plugin->save(); - $plugin->$name = $value; - - return $plugin->getGUID(); + return false; } - return false; + return $plugin->setSetting($name, $value); } /** * Get setting for a plugin. * - * @param string $name The name. - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. + * @param string $name The name of the setting. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. * * @return mixed + * @since 1.8.0 + * @todo make $plugin_id required in future version */ -function get_plugin_setting($name, $plugin_name = "") { - $plugin = find_plugin_settings($plugin_name); +function elgg_get_plugin_setting($name, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); + } - if ($plugin) { - return $plugin->$name; + if (!$plugin) { + return false; } - return false; + return $plugin->getSetting($name); } /** - * Clear a plugin setting. + * Unsets a plugin setting. * - * @param string $name The name. - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. + * @param string $name The name of the setting. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. * * @return bool + * @since 1.8.0 */ -function clear_plugin_setting($name, $plugin_name = "") { - $plugin = find_plugin_settings($plugin_name); +function elgg_unset_plugin_setting($name, $plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); + } - if ($plugin) { - return remove_private_setting($plugin->guid, $name); + if (!$plugin) { + return false; } - return FALSE; + return $plugin->unsetSetting($name); } /** - * Clear all plugin settings. + * Unsets all plugin settings for a plugin. * - * @param string $plugin_name Optional plugin name, if not specified - * then it is detected from where you are calling from. + * @param string $plugin_id Optional plugin name, if not specified + * then it is detected from where you are calling from. * * @return bool - * @since 1.7.0 + * @since 1.8.0 */ -function clear_all_plugin_settings($plugin_name = "") { - $plugin = find_plugin_settings($plugin_name); - - if ($plugin) { - return remove_all_private_settings($plugin->guid); +function elgg_unset_all_plugin_settings($plugin_id = null) { + if ($plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + } else { + $plugin = elgg_get_calling_plugin_entity(); } - return FALSE; -} - -/** - * Return an array of installed plugins. - * - * @return array - */ -function get_installed_plugins() { - global $CONFIG; - - $installed_plugins = array(); - - if (!empty($CONFIG->pluginspath)) { - $plugins = get_plugin_list(); - - foreach ($plugins as $mod) { - // require manifest. - if (!$manifest = load_plugin_manifest($mod)) { - continue; - } - $installed_plugins[$mod] = array(); - $installed_plugins[$mod]['active'] = is_plugin_enabled($mod); - $installed_plugins[$mod]['manifest'] = $manifest; - } + if (!$plugin) { + return false; } - return $installed_plugins; + return $plugin->unsetAllSettings(); } /** - * Enable a plugin for a site (default current site) + * Returns entities based upon plugin settings. + * Takes all the options for {@see elgg_get_entities_from_private_settings()} + * in addition to the ones below. * - * Important: You should regenerate simplecache and the viewpath cache after executing this function - * otherwise you may experience view display artifacts. Do this with the following code: + * @param array $options Array in the format: * - * elgg_view_regenerate_simplecache(); - * elgg_filepath_cache_reset(); + * plugin_id => NULL|STR The plugin id. Defaults to calling plugin * - * @param string $plugin The plugin name. - * @param int $site_guid The site id, if not specified then this is detected. + * plugin_user_setting_names => NULL|ARR private setting names * - * @return array - * @throws InvalidClassException + * plugin_user_setting_values => NULL|ARR metadata values + * + * plugin_user_setting_name_value_pairs => NULL|ARR ( + * name => 'name', + * value => 'value', + * 'operand' => '=', + * ) + * Currently if multiple values are sent via + * an array (value => array('value1', 'value2') + * the pair's operand will be forced to "IN". + * + * plugin_user_setting_name_value_pairs_operator => NULL|STR The operator to use for combining + * (name = value) OPERATOR (name = value); default AND + * + * @return mixed int If count, int. If not count, array. false on errors. */ -function enable_plugin($plugin, $site_guid = 0) { - global $CONFIG, $ENABLED_PLUGINS_CACHE; - - $plugin = sanitise_string($plugin); - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $site = get_entity($site_guid); - if (!($site instanceof ElggSite)) { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $site_guid, "ElggSite"); - throw new InvalidClassException($msg); +function elgg_get_entities_from_plugin_user_settings(array $options = array()) { + // if they're passing it don't bother + if (!isset($options['plugin_id'])) { + $options['plugin_id'] = elgg_get_calling_plugin_id(); } - if (!$plugin_info = load_plugin_manifest($plugin)) { - return FALSE; - } - - // getMetadata() doesn't return an array if only one plugin is enabled - if ($enabled = $site->enabled_plugins) { - if (!is_array($enabled)) { - $enabled = array($enabled); - } - } else { - $enabled = array(); - } + $singulars = array('plugin_user_setting_name', 'plugin_user_setting_value', + 'plugin_user_setting_name_value_pair'); - $enabled[] = $plugin; - $enabled = array_unique($enabled); + $options = elgg_normalise_plural_options_array($options, $singulars); - if ($return = $site->setMetaData('enabled_plugins', $enabled)) { + // rewrite plugin_user_setting_name_* to the right PS ones. + $map = array( + 'plugin_user_setting_names' => 'private_setting_names', + 'plugin_user_setting_values' => 'private_setting_values', + 'plugin_user_setting_name_value_pairs' => 'private_setting_name_value_pairs', + 'plugin_user_setting_name_value_pairs_operator' => 'private_setting_name_value_pairs_operator' + ); - // for other plugins that want to hook into this. - $params = array('plugin' => $plugin, 'manifest' => $plugin_info); - if ($return && !trigger_elgg_event('enable', 'plugin', $params)) { - $return = FALSE; + foreach ($map as $plugin => $private) { + if (!isset($options[$plugin])) { + continue; } - // for this plugin's on_enable - if ($return && isset($plugin_info['on_enable'])) { - // pull in the actual plugin's start so the on_enable function is callable - // NB: this will not run re-run the init hooks! - $start = "{$CONFIG->pluginspath}$plugin/start.php"; - if (!file_exists($start) || !include($start)) { - $return = FALSE; + if (isset($options[$private])) { + if (!is_array($options[$private])) { + $options[$private] = array($options[$private]); } - // need language files for the messages - $translations = "{$CONFIG->pluginspath}$plugin/languages/"; - register_translations($translations); - if (!is_callable($plugin_info['on_enable'])) { - $return = FALSE; - } else { - $on_enable = call_user_func($plugin_info['on_enable']); - // allow null to mean "I don't care" like other subsystems - $return = ($on_disable === FALSE) ? FALSE : TRUE; - } + $options[$private] = array_merge($options[$private], $options[$plugin]); + } else { + $options[$private] = $options[$plugin]; } + } - // disable the plugin if the on_enable or trigger results failed - if (!$return) { - array_pop($enabled); - $site->setMetaData('enabled_plugins', $enabled); - } - $ENABLED_PLUGINS_CACHE = $enabled; - } + $plugin_id = $options['plugin_id']; + $prefix = elgg_namespace_plugin_private_setting('user_setting', '', $plugin_id); + $options['private_setting_name_prefix'] = $prefix; - return $return; + return elgg_get_entities_from_private_settings($options); } /** - * Disable a plugin for a site (default current site) - * - * Important: You should regenerate simplecache and the viewpath cache after executing this function - * otherwise you may experience view display artifacts. Do this with the following code: + * Register object, plugin entities as ElggPlugin classes * - * elgg_view_regenerate_simplecache(); - * elgg_filepath_cache_reset(); + * @return void + * @access private + */ +function plugin_run_once() { + add_subtype("object", "plugin", "ElggPlugin"); +} + +/** + * Runs unit tests for the entity objects. * - * @param string $plugin The plugin name. - * @param int $site_guid The site id, if not specified then this is detected. + * @param string $hook unit_test + * @param string $type system + * @param mixed $value Array of tests + * @param mixed $params Params * - * @return bool - * @throws InvalidClassException + * @return array + * @access private */ -function disable_plugin($plugin, $site_guid = 0) { - global $CONFIG, $ENABLED_PLUGINS_CACHE; - - $plugin = sanitise_string($plugin); - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - $site = get_entity($site_guid); - if (!($site instanceof ElggSite)) { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $site_guid, "ElggSite"); - throw new InvalidClassException(); - } - - if (!$plugin_info = load_plugin_manifest($plugin)) { - return FALSE; - } - - // getMetadata() doesn't return an array if only one plugin is enabled - if ($enabled = $site->enabled_plugins) { - if (!is_array($enabled)) { - $enabled = array($enabled); - } - } else { - $enabled = array(); - } - - $old_enabled = $enabled; - - // remove the disabled plugin from the array - if (FALSE !== $i = array_search($plugin, $enabled)) { - unset($enabled[$i]); - } - - // if we're unsetting all the plugins, this will return an empty array. - // it will fail with FALSE, though. - $return = (FALSE === $site->enabled_plugins = $enabled) ? FALSE : TRUE; - - if ($return) { - // for other plugins that want to hook into this. - $params = array('plugin' => $plugin, 'manifest' => $plugin_info); - if ($return && !trigger_elgg_event('disable', 'plugin', $params)) { - $return = FALSE; - } - - // for this plugin's on_disable - if ($return && isset($plugin_info['on_disable'])) { - if (!is_callable($plugin_info['on_disable'])) { - $return = FALSE; - } else { - $on_disable = call_user_func($plugin_info['on_disable']); - // allow null to mean "I don't care" like other subsystems - $return = ($on_disable === FALSE) ? FALSE : TRUE; - } - } - - // disable the plugin if the on_enable or trigger results failed - if (!$return) { - $site->enabled_plugins = $old_enabled; - $ENABLED_PLUGINS_CACHE = $old_enabled; - } else { - $ENABLED_PLUGINS_CACHE = $enabled; - } - } - - return $return; +function plugins_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/plugins.php'; + return $value; } /** - * Return whether a plugin is enabled or not. + * Checks on deactivate plugin event if disabling it won't create unmet dependencies and blocks disable in such case. * - * @param string $plugin The plugin name. - * @param int $site_guid The site id, if not specified then this is detected. + * @param string $event deactivate + * @param string $type plugin + * @param array $params Parameters array containing entry with ELggPlugin instance under 'plugin_entity' key + * @return bool false to block plugin deactivation action * - * @return bool - * @throws InvalidClassException + * @access private */ -function is_plugin_enabled($plugin, $site_guid = 0) { - global $CONFIG, $ENABLED_PLUGINS_CACHE; +function _plugins_deactivate_dependency_check($event, $type, $params) { + $plugin_id = $params['plugin_entity']->getManifest()->getPluginID(); + $plugin_name = $params['plugin_entity']->getManifest()->getName(); - if (!file_exists($CONFIG->pluginspath . $plugin)) { - return false; - } + $active_plugins = elgg_get_plugins(); - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } + $dependents = array(); + foreach ($active_plugins as $plugin) { + $manifest = $plugin->getManifest(); + $requires = $manifest->getRequires(); - if (!$ENABLED_PLUGINS_CACHE) { - $site = get_entity($site_guid); - if (!($site instanceof ElggSite)) { - $msg = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $site_guid, "ElggSite"); - throw new InvalidClassException($msg); - } - - $enabled_plugins = $site->enabled_plugins; - if ($enabled_plugins && !is_array($enabled_plugins)) { - $enabled_plugins = array($enabled_plugins); + foreach ($requires as $required) { + if ($required['type'] == 'plugin' && $required['name'] == $plugin_id) { + // there are active dependents + $dependents[$manifest->getPluginID()] = $plugin; + } } - $ENABLED_PLUGINS_CACHE = $enabled_plugins; } - foreach ($ENABLED_PLUGINS_CACHE as $e) { - if ($e == $plugin) { - return true; + if ($dependents) { + $list = '<ul>'; + // construct error message and prevent disabling + foreach ($dependents as $dependent) { + $list .= '<li>' . $dependent->getManifest()->getName() . '</li>'; } - } + $list .= '</ul>'; - return false; -} + register_error(elgg_echo('ElggPlugin:Dependencies:ActiveDependent', array($plugin_name, $list))); -/** - * Register object, plugin entities as ElggPlugin classes - * - * @return void - */ -function plugin_run_once() { - // Register a class - add_subtype("object", "plugin", "ElggPlugin"); + return false; + } } /** - * Initialise the file modules. - * Listens to system boot and registers any appropriate file types and classes + * Initialize the plugin system + * Listens to system init and registers actions * * @return void + * @access private */ function plugin_init() { - // Now run this stuff, but only once run_function_once("plugin_run_once"); - // Register some actions - register_action("plugins/settings/save", false, "", true); - register_action("plugins/usersettings/save"); + elgg_register_plugin_hook_handler('unit_test', 'system', 'plugins_test'); + + // note - plugins are booted by the time this handler is registered + // deactivation due to error may have already occurred + elgg_register_event_handler('deactivate', 'plugin', '_plugins_deactivate_dependency_check'); + + elgg_register_action("plugins/settings/save", '', 'admin'); + elgg_register_action("plugins/usersettings/save"); + + elgg_register_action('admin/plugins/activate', '', 'admin'); + elgg_register_action('admin/plugins/deactivate', '', 'admin'); + elgg_register_action('admin/plugins/activate_all', '', 'admin'); + elgg_register_action('admin/plugins/deactivate_all', '', 'admin'); - register_action('admin/plugins/enable', false, "", true); - register_action('admin/plugins/disable', false, "", true); - register_action('admin/plugins/enableall', false, "", true); - register_action('admin/plugins/disableall', false, "", true); + elgg_register_action('admin/plugins/set_priority', '', 'admin'); - register_action('admin/plugins/reorder', false, "", true); + elgg_register_library('elgg:markdown', elgg_get_root_path() . 'vendors/markdown/markdown.php'); } -// Register a startup event -register_elgg_event_handler('init', 'system', 'plugin_init'); +elgg_register_event_handler('init', 'system', 'plugin_init'); diff --git a/engine/lib/private_settings.php b/engine/lib/private_settings.php new file mode 100644 index 000000000..7541f7b3b --- /dev/null +++ b/engine/lib/private_settings.php @@ -0,0 +1,414 @@ +<?php +/** + * Private settings for entities + * Private settings provide metadata like storage of settings for plugins + * and users. + * + * @package Elgg.Core + * @subpackage PrivateSettings + */ + +/** + * Returns entities based upon private settings. Also accepts all + * options available to elgg_get_entities(). Supports + * the singular option shortcut. + * + * @see elgg_get_entities + * + * @param array $options Array in format: + * + * private_setting_names => NULL|ARR private setting names + * + * private_setting_values => NULL|ARR metadata values + * + * private_setting_name_value_pairs => NULL|ARR ( + * name => 'name', + * value => 'value', + * 'operand' => '=', + * ) + * Currently if multiple values are sent via + * an array (value => array('value1', 'value2') + * the pair's operand will be forced to "IN". + * + * private_setting_name_value_pairs_operator => NULL|STR The operator to use for combining + * (name = value) OPERATOR (name = value); default AND + * + * private_setting_name_prefix => STR A prefix to apply to all private settings. Used to + * namespace plugin user settings or by plugins to namespace + * their own settings. + * + * + * @return mixed int If count, int. If not count, array. false on errors. + * @since 1.8.0 + */ +function elgg_get_entities_from_private_settings(array $options = array()) { + $defaults = array( + 'private_setting_names' => ELGG_ENTITIES_ANY_VALUE, + 'private_setting_values' => ELGG_ENTITIES_ANY_VALUE, + 'private_setting_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, + 'private_setting_name_value_pairs_operator' => 'AND', + 'private_setting_name_prefix' => '', + ); + + $options = array_merge($defaults, $options); + + $singulars = array('private_setting_name', 'private_setting_value', + 'private_setting_name_value_pair'); + + $options = elgg_normalise_plural_options_array($options, $singulars); + + $clauses = elgg_get_entity_private_settings_where_sql('e', $options['private_setting_names'], + $options['private_setting_values'], $options['private_setting_name_value_pairs'], + $options['private_setting_name_value_pairs_operator'], $options['private_setting_name_prefix']); + + if ($clauses) { + // merge wheres to pass to get_entities() + if (isset($options['wheres']) && !is_array($options['wheres'])) { + $options['wheres'] = array($options['wheres']); + } elseif (!isset($options['wheres'])) { + $options['wheres'] = array(); + } + + $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']); + + // merge joins to pass to get_entities() + if (isset($options['joins']) && !is_array($options['joins'])) { + $options['joins'] = array($options['joins']); + } elseif (!isset($options['joins'])) { + $options['joins'] = array(); + } + + $options['joins'] = array_merge($options['joins'], $clauses['joins']); + } + + return elgg_get_entities($options); +} + +/** + * Returns private setting name and value SQL where/join clauses for entities. + * + * @param string $table Entities table name + * @param array|null $names Array of names + * @param array|null $values Array of values + * @param array|null $pairs Array of names / values / operands + * @param string $pair_operator Operator for joining pairs where clauses + * @param string $name_prefix A string to prefix all names with + * @return array + * @since 1.8.0 + * @access private + */ +function elgg_get_entity_private_settings_where_sql($table, $names = NULL, $values = NULL, +$pairs = NULL, $pair_operator = 'AND', $name_prefix = '') { + + global $CONFIG; + + // @todo short circuit test + + $return = array ( + 'joins' => array (), + 'wheres' => array(), + ); + + $return['joins'][] = "JOIN {$CONFIG->dbprefix}private_settings ps on + {$table}.guid = ps.entity_guid"; + + $wheres = array(); + + // get names wheres + $names_where = ''; + if ($names !== NULL) { + if (!is_array($names)) { + $names = array($names); + } + + $sanitised_names = array(); + foreach ($names as $name) { + $name = $name_prefix . $name; + $sanitised_names[] = '\'' . sanitise_string($name) . '\''; + } + + $names_str = implode(',', $sanitised_names); + if ($names_str) { + $names_where = "(ps.name IN ($names_str))"; + } + } + + // get values wheres + $values_where = ''; + if ($values !== NULL) { + if (!is_array($values)) { + $values = array($values); + } + + $sanitised_values = array(); + foreach ($values as $value) { + // normalize to 0 + if (!$value) { + $value = 0; + } + $sanitised_values[] = '\'' . sanitise_string($value) . '\''; + } + + $values_str = implode(',', $sanitised_values); + if ($values_str) { + $values_where = "(ps.value IN ($values_str))"; + } + } + + if ($names_where && $values_where) { + $wheres[] = "($names_where AND $values_where)"; + } elseif ($names_where) { + $wheres[] = "($names_where)"; + } elseif ($values_where) { + $wheres[] = "($values_where)"; + } + + // add pairs which must be in arrays. + if (is_array($pairs)) { + // join counter for incremental joins in pairs + $i = 1; + + // check if this is an array of pairs or just a single pair. + if (isset($pairs['name']) || isset($pairs['value'])) { + $pairs = array($pairs); + } + + $pair_wheres = array(); + + foreach ($pairs as $index => $pair) { + // @todo move this elsewhere? + // support shortcut 'n' => 'v' method. + if (!is_array($pair)) { + $pair = array( + 'name' => $index, + 'value' => $pair + ); + } + + // must have at least a name and value + if (!isset($pair['name']) || !isset($pair['value'])) { + // @todo should probably return false. + continue; + } + + if (isset($pair['operand'])) { + $operand = sanitise_string($pair['operand']); + } else { + $operand = ' = '; + } + + // for comparing + $trimmed_operand = trim(strtolower($operand)); + + // if the value is an int, don't quote it because str '15' < str '5' + // if the operand is IN don't quote it because quoting should be done already. + if (is_numeric($pair['value'])) { + $value = sanitise_string($pair['value']); + } else if (is_array($pair['value'])) { + $values_array = array(); + + foreach ($pair['value'] as $pair_value) { + if (is_numeric($pair_value)) { + $values_array[] = sanitise_string($pair_value); + } else { + $values_array[] = "'" . sanitise_string($pair_value) . "'"; + } + } + + if ($values_array) { + $value = '(' . implode(', ', $values_array) . ')'; + } + + // @todo allow support for non IN operands with array of values. + // will have to do more silly joins. + $operand = 'IN'; + } else if ($trimmed_operand == 'in') { + $value = "({$pair['value']})"; + } else { + $value = "'" . sanitise_string($pair['value']) . "'"; + } + + $name = sanitise_string($name_prefix . $pair['name']); + + // @todo The multiple joins are only needed when the operator is AND + $return['joins'][] = "JOIN {$CONFIG->dbprefix}private_settings ps{$i} + on {$table}.guid = ps{$i}.entity_guid"; + + $pair_wheres[] = "(ps{$i}.name = '$name' AND ps{$i}.value + $operand $value)"; + + $i++; + } + + $where = implode(" $pair_operator ", $pair_wheres); + if ($where) { + $wheres[] = "($where)"; + } + } + + $where = implode(' AND ', $wheres); + if ($where) { + $return['wheres'][] = "($where)"; + } + + return $return; +} + +/** + * Gets a private setting for an entity. + * + * Plugin authors can set private data on entities. By default + * private data will not be searched or exported. + * + * @internal Private data is used to store settings for plugins + * and user settings. + * + * @param int $entity_guid The entity GUID + * @param string $name The name of the setting + * + * @return mixed The setting value, or false on failure + * @see set_private_setting() + * @see get_all_private_settings() + * @see remove_private_setting() + * @see remove_all_private_settings() + * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings + */ +function get_private_setting($entity_guid, $name) { + global $CONFIG; + $entity_guid = (int) $entity_guid; + $name = sanitise_string($name); + + $entity = get_entity($entity_guid); + if (!$entity instanceof ElggEntity) { + return false; + } + + $query = "SELECT value from {$CONFIG->dbprefix}private_settings + where name = '{$name}' and entity_guid = {$entity_guid}"; + $setting = get_data_row($query); + + if ($setting) { + return $setting->value; + } + return false; +} + +/** + * Return an array of all private settings. + * + * @param int $entity_guid The entity GUID + * + * @return array|false + * @see set_private_setting() + * @see get_private_settings() + * @see remove_private_setting() + * @see remove_all_private_settings() + * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings + */ +function get_all_private_settings($entity_guid) { + global $CONFIG; + + $entity_guid = (int) $entity_guid; + $entity = get_entity($entity_guid); + if (!$entity instanceof ElggEntity) { + return false; + } + + $query = "SELECT * from {$CONFIG->dbprefix}private_settings where entity_guid = {$entity_guid}"; + $result = get_data($query); + if ($result) { + $return = array(); + foreach ($result as $r) { + $return[$r->name] = $r->value; + } + + return $return; + } + + return false; +} + +/** + * Sets a private setting for an entity. + * + * @param int $entity_guid The entity GUID + * @param string $name The name of the setting + * @param string $value The value of the setting + * + * @return bool + * @see get_private_setting() + * @see get_all_private_settings() + * @see remove_private_setting() + * @see remove_all_private_settings() + * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings + */ +function set_private_setting($entity_guid, $name, $value) { + global $CONFIG; + + $entity_guid = (int) $entity_guid; + $name = sanitise_string($name); + $value = sanitise_string($value); + + $result = insert_data("INSERT into {$CONFIG->dbprefix}private_settings + (entity_guid, name, value) VALUES + ($entity_guid, '$name', '$value') + ON DUPLICATE KEY UPDATE value='$value'"); + + return $result !== false; +} + +/** + * Deletes a private setting for an entity. + * + * @param int $entity_guid The Entity GUID + * @param string $name The name of the setting + * + * @return bool + * @see get_private_setting() + * @see get_all_private_settings() + * @see set_private_setting() + * @see remove_all_private_settings() + * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings + */ +function remove_private_setting($entity_guid, $name) { + global $CONFIG; + + $entity_guid = (int) $entity_guid; + + $entity = get_entity($entity_guid); + if (!$entity instanceof ElggEntity) { + return false; + } + + $name = sanitise_string($name); + + return delete_data("DELETE from {$CONFIG->dbprefix}private_settings + WHERE name = '{$name}' + AND entity_guid = {$entity_guid}"); +} + +/** + * Deletes all private settings for an entity. + * + * @param int $entity_guid The Entity GUID + * + * @return bool + * @see get_private_setting() + * @see get_all_private_settings() + * @see set_private_setting() + * @see remove_private_settings() + * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings + */ +function remove_all_private_settings($entity_guid) { + global $CONFIG; + + $entity_guid = (int) $entity_guid; + + $entity = get_entity($entity_guid); + if (!$entity instanceof ElggEntity) { + return false; + } + + return delete_data("DELETE from {$CONFIG->dbprefix}private_settings + WHERE entity_guid = {$entity_guid}"); +} diff --git a/engine/lib/relationships.php b/engine/lib/relationships.php index 186e5a595..b0cd627fc 100644 --- a/engine/lib/relationships.php +++ b/engine/lib/relationships.php @@ -12,7 +12,8 @@ * * @param stdClass $row Database row from the relationship table * - * @return stdClass or ElggMetadata + * @return ElggRelationship|stdClass + * @access private */ function row_to_elggrelationship($row) { if (!($row instanceof stdClass)) { @@ -27,7 +28,7 @@ function row_to_elggrelationship($row) { * * @param int $id The ID of a relationship * - * @return mixed + * @return ElggRelationship|false */ function get_relationship($id) { global $CONFIG; @@ -52,7 +53,7 @@ function delete_relationship($id) { $relationship = get_relationship($id); - if (trigger_elgg_event('delete', 'relationship', $relationship)) { + if (elgg_trigger_event('delete', 'relationship', $relationship)) { return delete_data("delete from {$CONFIG->dbprefix}entity_relationships where id=$id"); } @@ -90,7 +91,7 @@ function add_entity_relationship($guid_one, $relationship, $guid_two) { if ($result !== false) { $obj = get_relationship($result); - if (trigger_elgg_event('create', $relationship, $obj)) { + if (elgg_trigger_event('create', $relationship, $obj)) { return true; } else { delete_relationship($result); @@ -108,7 +109,7 @@ function add_entity_relationship($guid_one, $relationship, $guid_two) { * @param string $relationship The type of relationship * @param int $guid_two The GUID of the entity the relationship is with * - * @return object|false Depending on success + * @return ElggRelationship|false Depending on success */ function check_entity_relationship($guid_one, $relationship, $guid_two) { global $CONFIG; @@ -122,7 +123,7 @@ function check_entity_relationship($guid_one, $relationship, $guid_two) { AND relationship='$relationship' AND guid_two=$guid_two limit 1"; - $row = $row = get_data_row($query); + $row = row_to_elggrelationship(get_data_row($query)); if ($row) { return $row; } @@ -151,13 +152,13 @@ function remove_entity_relationship($guid_one, $relationship, $guid_two) { return false; } - if (trigger_elgg_event('delete', $relationship, $obj)) { + if (elgg_trigger_event('delete', $relationship, $obj)) { $query = "DELETE from {$CONFIG->dbprefix}entity_relationships where guid_one=$guid_one and relationship='$relationship' and guid_two=$guid_two"; - return delete_data($query); + return (bool)delete_data($query); } else { return false; } @@ -219,7 +220,7 @@ function remove_entity_relationships($guid_one, $relationship = "", $inverse = f * @param int $guid The GUID of the relationship owner * @param bool $inverse_relationship Inverse relationship owners? * - * @return mixed + * @return ElggRelationship[] */ function get_entity_relationships($guid, $inverse_relationship = FALSE) { global $CONFIG; @@ -233,9 +234,22 @@ function get_entity_relationships($guid, $inverse_relationship = FALSE) { return get_data($query, "row_to_elggrelationship"); } - /** * Return entities matching a given query joining against a relationship. + * Also accepts all options available to elgg_get_entities() and + * elgg_get_entities_from_metadata(). + * + * To ask for entities that do not have a particulat relationship to an entity, + * use a custom where clause like the following: + * + * $options['wheres'][] = "NOT EXISTS ( + * SELECT 1 FROM {$db_prefix}entity_relationships + * WHERE guid_one = e.guid + * AND relationship = '$relationship' + * )"; + * + * @see elgg_get_entities + * @see elgg_get_entities_from_metadata * * @param array $options Array in format: * @@ -245,7 +259,7 @@ function get_entity_relationships($guid, $inverse_relationship = FALSE) { * * inverse_relationship => BOOL Inverse the relationship * - * @return array + * @return ElggEntity[]|mixed If count, int. If not count, array. false on errors. * @since 1.7.0 */ function elgg_get_entities_from_relationship($options) { @@ -257,7 +271,7 @@ function elgg_get_entities_from_relationship($options) { $options = array_merge($defaults, $options); - $clauses = elgg_get_entity_relationship_where_sql('e', $options['relationship'], + $clauses = elgg_get_entity_relationship_where_sql('e.guid', $options['relationship'], $options['relationship_guid'], $options['inverse_relationship']); if ($clauses) { @@ -278,6 +292,16 @@ function elgg_get_entities_from_relationship($options) { } $options['joins'] = array_merge($options['joins'], $clauses['joins']); + + if (isset($options['selects']) && !is_array($options['selects'])) { + $options['selects'] = array($options['selects']); + } elseif (!isset($options['selects'])) { + $options['selects'] = array(); + } + + $select = array('r.id'); + + $options['selects'] = array_merge($options['selects'], $select); } return elgg_get_entities_from_metadata($options); @@ -288,18 +312,20 @@ function elgg_get_entities_from_relationship($options) { * * @todo add support for multiple relationships and guids. * - * @param string $table Entities table name + * @param string $column Column name the guid should be checked against. + * Provide in table.column format. * @param string $relationship Relationship string * @param int $relationship_guid Entity guid to check - * @param string $inverse_relationship Inverse relationship check? + * @param bool $inverse_relationship Inverse relationship check? * * @return mixed * @since 1.7.0 + * @access private */ -function elgg_get_entity_relationship_where_sql($table, $relationship = NULL, +function elgg_get_entity_relationship_where_sql($column, $relationship = NULL, $relationship_guid = NULL, $inverse_relationship = FALSE) { - if ($relationship == NULL && $entity_guid == NULL) { + if ($relationship == NULL && $relationship_guid == NULL) { return ''; } @@ -309,9 +335,9 @@ $relationship_guid = NULL, $inverse_relationship = FALSE) { $joins = array(); if ($inverse_relationship) { - $joins[] = "JOIN {$CONFIG->dbprefix}entity_relationships r on r.guid_one = e.guid"; + $joins[] = "JOIN {$CONFIG->dbprefix}entity_relationships r on r.guid_one = $column"; } else { - $joins[] = "JOIN {$CONFIG->dbprefix}entity_relationships r on r.guid_two = e.guid"; + $joins[] = "JOIN {$CONFIG->dbprefix}entity_relationships r on r.guid_two = $column"; } if ($relationship) { @@ -335,74 +361,10 @@ $relationship_guid = NULL, $inverse_relationship = FALSE) { } /** - * Return entities from relationships - * - * @deprecated 1.7 Use elgg_get_entities_from_relationship() - * - * @param string $relationship The relationship type - * @param int $relationship_guid The GUID of the relationship owner - * @param bool $inverse_relationship Invert relationship? - * @param string $type Entity type - * @param string $subtype Entity subtype - * @param int $owner_guid Entity owner GUID - * @param string $order_by Order by clause - * @param int $limit Limit - * @param int $offset Offset - * @param bool $count Return a count instead of entities? - * @param int $site_guid Site GUID - * - * @return mixed - */ -function get_entities_from_relationship($relationship, $relationship_guid, -$inverse_relationship = false, $type = "", $subtype = "", $owner_guid = 0, -$order_by = "", $limit = 10, $offset = 0, $count = false, $site_guid = 0) { - - elgg_deprecated_notice('get_entities_from_relationship() was deprecated by elgg_get_entities_from_relationship()!', 1.7); - - $options = array(); - - $options['relationship'] = $relationship; - $options['relationship_guid'] = $relationship_guid; - $options['inverse_relationship'] = $inverse_relationship; - - if ($type) { - $options['types'] = $type; - } - - if ($subtype) { - $options['subtypes'] = $subtype; - } - - if ($owner_guid) { - $options['owner_guid'] = $owner_guid; - } - - $options['limit'] = $limit; - - if ($offset) { - $options['offset'] = $offset; - } - - if ($order_by) { - $options['order_by']; - } - - if ($site_guid) { - $options['site_guid']; - } - - if ($count) { - $options['count'] = $count; - } - - return elgg_get_entities_from_relationship($options); -} - -/** * Returns a viewable list of entities by relationship * - * @param array $options - * + * @param array $options Options array for retrieval of entities + * * @see elgg_list_entities() * @see elgg_get_entities_from_relationship() * @@ -413,239 +375,48 @@ function elgg_list_entities_from_relationship(array $options = array()) { } /** - * @deprecated 1.8 Use elgg_list_entities_from_relationship() - */ -function list_entities_from_relationship($relationship, $relationship_guid, -$inverse_relationship = false, $type = ELGG_ENTITIES_ANY_VALUE, -$subtype = ELGG_ENTITIES_ANY_VALUE, $owner_guid = 0, $limit = 10, -$fullview = true, $viewtypetoggle = false, $pagination = true) { - - elgg_deprecated_notice("list_entities_from_relationship was deprecated by elgg_list_entities_from_relationship()!", 1.8); - return elgg_list_entities_from_relationship(array( - 'relationship' => $relationship, - 'relationship_guid' => $relationship_guid, - 'inverse_relationship' => $inverse_relationship, - 'types' => $type, - 'subtypes' => $subtype, - 'owner_guid' => $owner_guid, - 'order_by' => '', - 'limit' => $limit, - 'count' => TRUE - )); -} - -/** * Gets the number of entities by a the number of entities related to them in a particular way. * This is a good way to get out the users with the most friends, or the groups with the * most members. * - * @param string $relationship The relationship eg "friends_of" - * @param bool $inverse_relationship Inverse relationship owners - * @param string $type The type of entity (default: all) - * @param string $subtype The entity subtype (default: all) - * @param int $owner_guid The owner of the entities (default: none) - * @param int $limit Limit - * @param int $offset Offset - * @param bool $count Return a count instead of entities - * @param int $site_guid Site GUID - * - * @return array|int|false An array of entities, or the number of entities, or false on failure + * @param array $options An options array compatible with + * elgg_get_entities_from_relationship() + * @return ElggEntity[]|mixed int If count, int. If not count, array. false on errors. + * @since 1.8.0 */ - -function get_entities_by_relationship_count($relationship, $inverse_relationship = true, $type = "", -$subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $count = false, $site_guid = 0) { - - global $CONFIG; - - $relationship = sanitise_string($relationship); - $inverse_relationship = (bool)$inverse_relationship; - $type = sanitise_string($type); - if ($subtype AND !$subtype = get_subtype_id($type, $subtype)) { - return false; - } - $owner_guid = (int)$owner_guid; - $order_by = sanitise_string($order_by); - $limit = (int)$limit; - $offset = (int)$offset; - $site_guid = (int) $site_guid; - if ($site_guid == 0) { - $site_guid = $CONFIG->site_guid; - } - - //$access = get_access_list(); - - $where = array(); - - if ($relationship != "") { - $where[] = "r.relationship='$relationship'"; - } - - if ($inverse_relationship) { - $on = 'e.guid = r.guid_two'; - } else { - $on = 'e.guid = r.guid_one'; - } - if ($type != "") { - $where[] = "e.type='$type'"; - } - - if ($subtype) { - $where[] = "e.subtype=$subtype"; - } - - if ($owner_guid != "") { - $where[] = "e.container_guid='$owner_guid'"; - } - - if ($site_guid > 0) { - $where[] = "e.site_guid = {$site_guid}"; - } - - if ($count) { - $query = "SELECT count(distinct e.guid) as total "; - } else { - $query = "SELECT e.*, count(e.guid) as total "; - } - - $query .= " from {$CONFIG->dbprefix}entity_relationships r - JOIN {$CONFIG->dbprefix}entities e on {$on} where "; - - if (!empty($where)) { - foreach ($where as $w) { - $query .= " $w and "; - } - } - $query .= get_access_sql_suffix("e"); - - if (!$count) { - $query .= " group by e.guid "; - $query .= " order by total desc limit {$offset}, {$limit}"; - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } - } - - return false; +function elgg_get_entities_from_relationship_count(array $options = array()) { + $options['selects'][] = "COUNT(e.guid) as total"; + $options['group_by'] = 'r.guid_two'; + $options['order_by'] = 'total desc'; + return elgg_get_entities_from_relationship($options); } /** - * Displays a human-readable list of entities - * - * @param string $relationship The relationship eg "friends_of" - * @param bool $inverse_relationship Inverse relationship owners - * @param string $type The type of entity (eg 'object') - * @param string $subtype The entity subtype - * @param int $owner_guid The owner (default: all) - * @param int $limit The number of entities to display on a page - * @param bool $fullview Whether or not to display the full view (default: true) - * @param bool $viewtypetoggle Whether or not to allow gallery view - * @param bool $pagination Whether to display pagination (default: true) + * Returns a list of entities by relationship count * - * @return string The viewable list of entities - */ - -function list_entities_by_relationship_count($relationship, $inverse_relationship = true, -$type = "", $subtype = "", $owner_guid = 0, $limit = 10, $fullview = true, -$viewtypetoggle = false, $pagination = true) { - - $limit = (int) $limit; - $offset = (int) get_input('offset'); - $count = get_entities_by_relationship_count($relationship, $inverse_relationship, - $type, $subtype, $owner_guid, 0, 0, true); - $entities = get_entities_by_relationship_count($relationship, $inverse_relationship, - $type, $subtype, $owner_guid, $limit, $offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, $pagination); -} - -/** - * Gets the number of entities by a the number of entities related to - * them in a particular way also constrained by metadata - * - * @param string $relationship The relationship eg "friends_of" - * @param int $relationship_guid The guid of the entity to use query - * @param bool $inverse_relationship Inverse relationship owner - * @param String $meta_name The metadata name - * @param String $meta_value The metadata value - * @param string $type The type of entity (default: all) - * @param string $subtype The entity subtype (default: all) - * @param int $owner_guid The owner of the entities (default: none) - * @param int $limit Limit - * @param int $offset Offset - * @param bool $count Return a count instead of entities - * @param int $site_guid Site GUID - * - * @return array|int|false An array of entities, or the number of entities, or false on failure + * @see elgg_get_entities_from_relationship_count() + * + * @param array $options Options array + * + * @return string + * @since 1.8.0 */ -function get_entities_from_relationships_and_meta($relationship, $relationship_guid, -$inverse_relationship = false, $meta_name = "", $meta_value = "", $type = "", -$subtype = "", $owner_guid = 0, $limit = 10, $offset = 0, $count = false, $site_guid = 0) { - - elgg_deprecated_notice('get_entities_from_relationship_and_meta() was deprecated by elgg_get_entities_from_relationship()!', 1.7); - - $options = array(); - - $options['relationship'] = $relationship; - $options['relationship_guid'] = $relationship_guid; - $options['inverse_relationship'] = $inverse_relationship; - - if ($meta_value) { - $options['values'] = $meta_value; - } - - if ($entity_type) { - $options['types'] = $entity_type; - } - - if ($type) { - $options['types'] = $type; - } - - if ($subtype) { - $options['subtypes'] = $subtype; - } - - if ($owner_guid) { - $options['owner_guid'] = $owner_guid; - } - - if ($limit) { - $options['limit'] = $limit; - } - - if ($offset) { - $options['offset'] = $offset; - } - - if ($order_by) { - $options['order_by']; - } - - if ($site_guid) { - $options['site_guid']; - } - - if ($count) { - $options['count'] = $count; - } - - return elgg_get_entities_from_relationship($options); +function elgg_list_entities_from_relationship_count($options) { + return elgg_list_entities($options, 'elgg_get_entities_from_relationship_count'); } /** * Sets the URL handler for a particular relationship type * - * @param string $function_name The function to register * @param string $relationship_type The relationship type. + * @param string $function_name The function to register * * @return bool Depending on success */ -function register_relationship_url_handler($function_name, $relationship_type = "all") { +function elgg_register_relationship_url_handler($relationship_type, $function_name) { global $CONFIG; - if (!is_callable($function_name)) { + if (!is_callable($function_name, true)) { return false; } @@ -687,7 +458,7 @@ function get_relationship_url($id) { } if (is_callable($function)) { - $url = $function($relationship); + $url = call_user_func($function, $relationship); } if ($url == "") { @@ -712,7 +483,8 @@ function get_relationship_url($id) { * @param int $guid_two This is the object trying to attach to $guid_one * * @return bool - **/ + * @access private + */ function already_attached($guid_one, $guid_two) { if ($attached = check_entity_relationship($guid_one, "attached", $guid_two)) { return true; @@ -727,14 +499,15 @@ function already_attached($guid_one, $guid_two) { * @param int $guid Entity GUID * @param string $type The type of object to return e.g. 'file', 'friend_of' etc * - * @return an array of objects -**/ + * @return ElggEntity[] + * @access private + */ function get_attachments($guid, $type = "") { $options = array( 'relationship' => 'attached', 'relationship_guid' => $guid, 'inverse_relationship' => false, - 'types' => $type, + 'type' => $type, 'subtypes' => '', 'owner_guid' => 0, 'order_by' => 'time_created desc', @@ -754,7 +527,8 @@ function get_attachments($guid, $type = "") { * @param int $guid_two This is the object to remove from $guid_one * * @return void -**/ + * @access private + */ function remove_attachment($guid_one, $guid_two) { if (already_attached($guid_one, $guid_two)) { remove_entity_relationship($guid_one, "attached", $guid_two); @@ -768,7 +542,8 @@ function remove_attachment($guid_one, $guid_two) { * @param int $guid_two This is the object trying to attach to $guid_one * * @return true|void -**/ + * @access private + */ function make_attachment($guid_one, $guid_two) { if (!(already_attached($guid_one, $guid_two))) { if (add_entity_relationship($guid_one, "attached", $guid_two)) { @@ -786,7 +561,7 @@ function make_attachment($guid_one, $guid_two) { * @param mixed $params Array of params * * @return mixed - * + * @access private */ function import_relationship_plugin_hook($hook, $entity_type, $returnvalue, $params) { $element = $params['element']; @@ -796,9 +571,8 @@ function import_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par if ($element instanceof ODDRelationship) { $tmp = new ElggRelationship(); $tmp->import($element); - - return $tmp; } + return $tmp; } /** @@ -811,10 +585,10 @@ function import_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par * * @elgg_event_handler export all * @return mixed + * @throws InvalidParameterException + * @access private */ function export_relationship_plugin_hook($hook, $entity_type, $returnvalue, $params) { - global $CONFIG; - // Sanity check values if ((!is_array($params)) && (!isset($params['guid']))) { throw new InvalidParameterException(elgg_echo('InvalidParameterException:GUIDNotForExport')); @@ -838,38 +612,32 @@ function export_relationship_plugin_hook($hook, $entity_type, $returnvalue, $par } /** - * An event listener which will notify users based on certain events. + * Notify user that someone has friended them * - * @param string $event Event name - * @param string $object_type Object type - * @param mixed $object Object + * @param string $event Event name + * @param string $type Object type + * @param mixed $object Object * * @return bool + * @access private */ -function relationship_notification_hook($event, $object_type, $object) { - global $CONFIG; - - if ( - ($object instanceof ElggRelationship) && - ($event == 'create') && - ($object_type == 'friend') - ) { - $user_one = get_entity($object->guid_one); - $user_two = get_entity($object->guid_two); - - // Notify target user - return notify_user($object->guid_two, $object->guid_one, - sprintf(elgg_echo('friend:newfriend:subject'), $user_one->name), - sprintf(elgg_echo("friend:newfriend:body"), $user_one->name, $user_one->getURL()) - ); - } +function relationship_notification_hook($event, $type, $object) { + /* @var ElggRelationship $object */ + $user_one = get_entity($object->guid_one); + /* @var ElggUser $user_one */ + + return notify_user($object->guid_two, + $object->guid_one, + elgg_echo('friend:newfriend:subject', array($user_one->name)), + elgg_echo("friend:newfriend:body", array($user_one->name, $user_one->getURL())) + ); } -/** Register the import hook */ -register_plugin_hook("import", "all", "import_relationship_plugin_hook", 3); +// Register the import hook +elgg_register_plugin_hook_handler("import", "all", "import_relationship_plugin_hook", 3); -/** Register the hook, ensuring entities are serialised first */ -register_plugin_hook("export", "all", "export_relationship_plugin_hook", 3); +// Register the hook, ensuring entities are serialised first +elgg_register_plugin_hook_handler("export", "all", "export_relationship_plugin_hook", 3); -/** Register event to listen to some events **/ -register_elgg_event_handler('create', 'friend', 'relationship_notification_hook'); +// Register event to listen to some events +elgg_register_event_handler('create', 'friend', 'relationship_notification_hook'); diff --git a/engine/lib/river.php b/engine/lib/river.php index ad069d5cf..e92040eb7 100644 --- a/engine/lib/river.php +++ b/engine/lib/river.php @@ -1,7 +1,7 @@ <?php /** - * Elgg river 2.0. - * Functions for listening for and generating the river separately from the system log. + * Elgg river. + * Activity stream functions. * * @package Elgg.Core * @subpackage SocialModel.River @@ -18,12 +18,14 @@ * @param int $posted The UNIX epoch timestamp of the river item (default: now) * @param int $annotation_id The annotation ID associated with this river entry * - * @return bool Depending on success + * @return int/bool River ID or false on failure */ function add_to_river($view, $action_type, $subject_guid, $object_guid, $access_id = "", $posted = 0, $annotation_id = 0) { - // use default viewtype for when called from REST api + global $CONFIG; + + // use default viewtype for when called from web services api if (!elgg_view_exists($view, 'default')) { return false; } @@ -42,705 +44,660 @@ $posted = 0, $annotation_id = 0) { if ($access_id === "") { $access_id = $object->access_id; } - $annotation_id = (int)$annotation_id; $type = $object->getType(); $subtype = $object->getSubtype(); - $action_type = sanitise_string($action_type); - // Load config - global $CONFIG; + $view = sanitise_string($view); + $action_type = sanitise_string($action_type); + $subject_guid = sanitise_int($subject_guid); + $object_guid = sanitise_int($object_guid); + $access_id = sanitise_int($access_id); + $posted = sanitise_int($posted); + $annotation_id = sanitise_int($annotation_id); + + $values = array( + 'type' => $type, + 'subtype' => $subtype, + 'action_type' => $action_type, + 'access_id' => $access_id, + 'view' => $view, + 'subject_guid' => $subject_guid, + 'object_guid' => $object_guid, + 'annotation_id' => $annotation_id, + 'posted' => $posted, + ); + + // return false to stop insert + $values = elgg_trigger_plugin_hook('creating', 'river', null, $values); + if ($values == false) { + // inserting did not fail - it was just prevented + return true; + } + + extract($values); // Attempt to save river item; return success status - $insert_data = insert_data("insert into {$CONFIG->dbprefix}river " . - " set type = '{$type}', " . - " subtype = '{$subtype}', " . - " action_type = '{$action_type}', " . - " access_id = {$access_id}, " . - " view = '{$view}', " . - " subject_guid = {$subject_guid}, " . - " object_guid = {$object_guid}, " . - " annotation_id = {$annotation_id}, " . - " posted = {$posted} "); - - //update the entities which had the action carried out on it - if ($insert_data) { + $id = insert_data("insert into {$CONFIG->dbprefix}river " . + " set type = '$type', " . + " subtype = '$subtype', " . + " action_type = '$action_type', " . + " access_id = $access_id, " . + " view = '$view', " . + " subject_guid = $subject_guid, " . + " object_guid = $object_guid, " . + " annotation_id = $annotation_id, " . + " posted = $posted"); + + // update the entities which had the action carried out on it + // @todo shouldn't this be down elsewhere? Like when an annotation is saved? + if ($id) { update_entity_last_action($object_guid, $posted); - return $insert_data; + + $river_items = elgg_get_river(array('id' => $id)); + if ($river_items) { + elgg_trigger_event('created', 'river', $river_items[0]); + } + return $id; + } else { + return false; } } /** - * Removes all items relating to a particular acting entity from the river + * Delete river items * - * @param int $subject_guid The GUID of the entity + * @warning not checking access (should we?) * - * @return bool Depending on success - */ -function remove_from_river_by_subject($subject_guid) { - // Sanitise - $subject_guid = (int) $subject_guid; - - // Load config - global $CONFIG; - - // Remove - return delete_data("delete from {$CONFIG->dbprefix}river where subject_guid = {$subject_guid}"); -} - -/** - * Removes all items relating to a particular entity being acted upon from the river + * @param array $options Parameters: + * ids => INT|ARR River item id(s) + * subject_guids => INT|ARR Subject guid(s) + * object_guids => INT|ARR Object guid(s) + * annotation_ids => INT|ARR The identifier of the annotation(s) + * action_types => STR|ARR The river action type(s) identifier + * views => STR|ARR River view(s) * - * @param int $object_guid The GUID of the entity + * types => STR|ARR Entity type string(s) + * subtypes => STR|ARR Entity subtype string(s) + * type_subtype_pairs => ARR Array of type => subtype pairs where subtype + * can be an array of subtype strings * - * @return bool Depending on success + * posted_time_lower => INT The lower bound on the time posted + * posted_time_upper => INT The upper bound on the time posted + * + * @return bool + * @since 1.8.0 */ -function remove_from_river_by_object($object_guid) { - // Sanitise - $object_guid = (int) $object_guid; - - // Load config +function elgg_delete_river(array $options = array()) { global $CONFIG; - // Remove - return delete_data("delete from {$CONFIG->dbprefix}river where object_guid = {$object_guid}"); -} + $defaults = array( + 'ids' => ELGG_ENTITIES_ANY_VALUE, -/** - * Removes all items relating to a particular annotation being acted upon from the river - * - * @param int $annotation_id The ID of the annotation - * - * @return bool Depending on success - * @since 1.7.0 - */ -function remove_from_river_by_annotation($annotation_id) { - // Sanitise - $annotation_id = (int) $annotation_id; + 'subject_guids' => ELGG_ENTITIES_ANY_VALUE, + 'object_guids' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_ids' => ELGG_ENTITIES_ANY_VALUE, - // Load config - global $CONFIG; + 'views' => ELGG_ENTITIES_ANY_VALUE, + 'action_types' => ELGG_ENTITIES_ANY_VALUE, - // Remove - return delete_data("delete from {$CONFIG->dbprefix}river where annotation_id = {$annotation_id}"); -} + 'types' => ELGG_ENTITIES_ANY_VALUE, + 'subtypes' => ELGG_ENTITIES_ANY_VALUE, + 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE, -/** - * Removes a single river entry - * - * @param int $id The ID of the river entry - * - * @return bool Depending on success - * @since 1.7.2 - */ -function remove_from_river_by_id($id) { - global $CONFIG; + 'posted_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'posted_time_upper' => ELGG_ENTITIES_ANY_VALUE, - // Sanitise - $id = (int) $id; + 'wheres' => array(), + 'joins' => array(), - return delete_data("delete from {$CONFIG->dbprefix}river where id = {$id}"); -} + ); -/** - * Sets the access ID on river items for a particular object - * - * @param int $object_guid The GUID of the entity - * @param int $access_id The access ID - * - * @return bool Depending on success - */ -function update_river_access_by_object($object_guid, $access_id) { - // Sanitise - $object_guid = (int) $object_guid; - $access_id = (int) $access_id; + $options = array_merge($defaults, $options); - // Load config - global $CONFIG; + $singulars = array('id', 'subject_guid', 'object_guid', 'annotation_id', 'action_type', 'view', 'type', 'subtype'); + $options = elgg_normalise_plural_options_array($options, $singulars); - // Remove - $query = "update {$CONFIG->dbprefix}river - set access_id = {$access_id} - where object_guid = {$object_guid}"; - return update_data($query); -} + $wheres = $options['wheres']; -/** - * Retrieves items from the river. All parameters are optional. - * - * @param int|array $subject_guid Acting entity to restrict to. Default: all - * @param int|array $object_guid Entity being acted on to restrict to. Default: all - * @param string $subject_relationship If set to a relationship type, this will use - * $subject_guid as the starting point and set the - * subjects to be all users this - * entity has this relationship with (eg 'friend'). - * Default: blank - * @param string $type The type of entity to restrict to. Default: all - * @param string $subtype The subtype of entity to restrict to. Default: all - * @param string $action_type The type of river action to restrict to. Default: all - * @param int $limit The number of items to retrieve. Default: 20 - * @param int $offset The page offset. Default: 0 - * @param int $posted_min The minimum time period to look at. Default: none - * @param int $posted_max The maximum time period to look at. Default: none - * - * @return array|false Depending on success - */ -function get_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', -$type = '', $subtype = '', $action_type = '', $limit = 20, $offset = 0, $posted_min = 0, -$posted_max = 0) { + $wheres[] = elgg_get_guid_based_where_sql('rv.id', $options['ids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.subject_guid', $options['subject_guids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.object_guid', $options['object_guids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.annotation_id', $options['annotation_ids']); + $wheres[] = elgg_river_get_action_where_sql($options['action_types']); + $wheres[] = elgg_river_get_view_where_sql($options['views']); + $wheres[] = elgg_get_river_type_subtype_where_sql('rv', $options['types'], + $options['subtypes'], $options['type_subtype_pairs']); - // Get config - global $CONFIG; + if ($options['posted_time_lower'] && is_int($options['posted_time_lower'])) { + $wheres[] = "rv.posted >= {$options['posted_time_lower']}"; + } - // Sanitise variables - if (!is_array($subject_guid)) { - $subject_guid = (int) $subject_guid; - } else { - foreach ($subject_guid as $key => $temp) { - $subject_guid[$key] = (int) $temp; - } + if ($options['posted_time_upper'] && is_int($options['posted_time_upper'])) { + $wheres[] = "rv.posted <= {$options['posted_time_upper']}"; } - if (!is_array($object_guid)) { - $object_guid = (int) $object_guid; - } else { - foreach ($object_guid as $key => $temp) { - $object_guid[$key] = (int) $temp; + + // see if any functions failed + // remove empty strings on successful functions + foreach ($wheres as $i => $where) { + if ($where === FALSE) { + return FALSE; + } elseif (empty($where)) { + unset($wheres[$i]); } } - if (!empty($type)) { - $type = sanitise_string($type); - } - if (!empty($subtype)) { - $subtype = sanitise_string($subtype); - } - if (!empty($action_type)) { - $action_type = sanitise_string($action_type); - } - $limit = (int) $limit; - $offset = (int) $offset; - $posted_min = (int) $posted_min; - $posted_max = (int) $posted_max; - // Construct 'where' clauses for the river - $where = array(); - // river table does not have columns expected by get_access_sql_suffix so we modify its output - $where[] = str_replace("and enabled='yes'", '', - str_replace('owner_guid', 'subject_guid', get_access_sql_suffix())); + // remove identical where clauses + $wheres = array_unique($wheres); - if (empty($subject_relationship)) { - if (!empty($subject_guid)) { - if (!is_array($subject_guid)) { - $where[] = " subject_guid = {$subject_guid} "; - } else { - $where[] = " subject_guid in (" . implode(',', $subject_guid) . ") "; - } - } - } else { - if (!is_array($subject_guid)) { - if ($entities = elgg_get_entities_from_relationship(array ( - 'relationship' => $subject_relationship, - 'relationship_guid' => $subject_guid, - 'limit' => 9999)) - ) { - $guids = array(); - foreach ($entities as $entity) { - $guids[] = (int) $entity->guid; - } - $where[] = " subject_guid in (" . implode(',', $guids) . ") "; - } else { - return array(); - } - } - } - if (!empty($object_guid)) { - if (!is_array($object_guid)) { - $where[] = " object_guid = {$object_guid} "; - } else { - $where[] = " object_guid in (" . implode(',', $object_guid) . ") "; - } - } - if (!empty($type)) { - $where[] = " type = '{$type}' "; - } - if (!empty($subtype)) { - $where[] = " subtype = '{$subtype}' "; - } - if (!empty($action_type)) { - $where[] = " action_type = '{$action_type}' "; - } - if (!empty($posted_min)) { - $where[] = " posted > {$posted_min} "; - } - if (!empty($posted_max)) { - $where[] = " posted < {$posted_max} "; + $query = "DELETE rv.* FROM {$CONFIG->dbprefix}river rv "; + + // remove identical join clauses + $joins = array_unique($options['joins']); + + // add joins + foreach ($joins as $j) { + $query .= " $j "; } - $whereclause = implode(' and ', $where); + // add wheres + $query .= ' WHERE '; - // Construct main SQL - $sql = "select id, type, subtype, action_type, access_id, view, - subject_guid, object_guid, annotation_id, posted - from {$CONFIG->dbprefix}river - where {$whereclause} order by posted desc limit {$offset}, {$limit}"; + foreach ($wheres as $w) { + $query .= " $w AND "; + } + $query .= "1=1"; - // Get data - return get_data($sql); + return delete_data($query); } /** - * Retrieves items from the river. All parameters are optional. + * Get river items + * + * @note If using types and subtypes in a query, they are joined with an AND. + * + * @param array $options Parameters: + * ids => INT|ARR River item id(s) + * subject_guids => INT|ARR Subject guid(s) + * object_guids => INT|ARR Object guid(s) + * annotation_ids => INT|ARR The identifier of the annotation(s) + * action_types => STR|ARR The river action type(s) identifier + * posted_time_lower => INT The lower bound on the time posted + * posted_time_upper => INT The upper bound on the time posted + * + * types => STR|ARR Entity type string(s) + * subtypes => STR|ARR Entity subtype string(s) + * type_subtype_pairs => ARR Array of type => subtype pairs where subtype + * can be an array of subtype strings * - * @param int|array $subject_guid Acting entity to restrict to. Default: all - * @param int|array $object_guid Entity being acted on to restrict to. Default: all - * @param string $subject_relationship If set to a relationship type, this will use - * $subject_guid as the starting point and set the - * subjects to be all users this entity has this - * relationship with (eg 'friend'). Default: blank - * @param string $type The type of entity to restrict to. Default: all - * @param string $subtype The subtype of entity to restrict to. Default: all - * @param string $action_type The type of river action to restrict to. Default: all - * @param int $limit The number of items to retrieve. Default: 20 - * @param int $offset The page offset. Default: 0 - * @param int $posted_min The minimum time period to look at. Default: none - * @param int $posted_max The maximum time period to look at. Default: none + * relationship => STR Relationship identifier + * relationship_guid => INT|ARR Entity guid(s) + * inverse_relationship => BOOL Subject or object of the relationship (false) * - * @return array|false Depending on success + * limit => INT Number to show per page (20) + * offset => INT Offset in list (0) + * count => BOOL Count the river items? (false) + * order_by => STR Order by clause (rv.posted desc) + * group_by => STR Group by clause + * + * @return array|int + * @since 1.8.0 */ -function elgg_get_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', -$type = '', $subtype = '', $action_type = '', $limit = 10, $offset = 0, $posted_min = 0, -$posted_max = 0) { - - // Get config +function elgg_get_river(array $options = array()) { global $CONFIG; - // Sanitise variables - if (!is_array($subject_guid)) { - $subject_guid = (int) $subject_guid; - } else { - foreach ($subject_guid as $key => $temp) { - $subject_guid[$key] = (int) $temp; - } - } - if (!is_array($object_guid)) { - $object_guid = (int) $object_guid; - } else { - foreach ($object_guid as $key => $temp) { - $object_guid[$key] = (int) $temp; - } - } - if (!empty($type)) { - $type = sanitise_string($type); - } - if (!empty($subtype)) { - $subtype = sanitise_string($subtype); + $defaults = array( + 'ids' => ELGG_ENTITIES_ANY_VALUE, + + 'subject_guids' => ELGG_ENTITIES_ANY_VALUE, + 'object_guids' => ELGG_ENTITIES_ANY_VALUE, + 'annotation_ids' => ELGG_ENTITIES_ANY_VALUE, + 'action_types' => ELGG_ENTITIES_ANY_VALUE, + + 'relationship' => NULL, + 'relationship_guid' => NULL, + 'inverse_relationship' => FALSE, + + 'types' => ELGG_ENTITIES_ANY_VALUE, + 'subtypes' => ELGG_ENTITIES_ANY_VALUE, + 'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE, + + 'posted_time_lower' => ELGG_ENTITIES_ANY_VALUE, + 'posted_time_upper' => ELGG_ENTITIES_ANY_VALUE, + + 'limit' => 20, + 'offset' => 0, + 'count' => FALSE, + + 'order_by' => 'rv.posted desc', + 'group_by' => ELGG_ENTITIES_ANY_VALUE, + + 'wheres' => array(), + 'joins' => array(), + ); + + $options = array_merge($defaults, $options); + + $singulars = array('id', 'subject_guid', 'object_guid', 'annotation_id', 'action_type', 'type', 'subtype'); + $options = elgg_normalise_plural_options_array($options, $singulars); + + $wheres = $options['wheres']; + + $wheres[] = elgg_get_guid_based_where_sql('rv.id', $options['ids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.subject_guid', $options['subject_guids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.object_guid', $options['object_guids']); + $wheres[] = elgg_get_guid_based_where_sql('rv.annotation_id', $options['annotation_ids']); + $wheres[] = elgg_river_get_action_where_sql($options['action_types']); + $wheres[] = elgg_get_river_type_subtype_where_sql('rv', $options['types'], + $options['subtypes'], $options['type_subtype_pairs']); + + if ($options['posted_time_lower'] && is_int($options['posted_time_lower'])) { + $wheres[] = "rv.posted >= {$options['posted_time_lower']}"; } - if (!empty($action_type)) { - $action_type = sanitise_string($action_type); + + if ($options['posted_time_upper'] && is_int($options['posted_time_upper'])) { + $wheres[] = "rv.posted <= {$options['posted_time_upper']}"; } - $limit = (int) $limit; - $offset = (int) $offset; - $posted_min = (int) $posted_min; - $posted_max = (int) $posted_max; - // Construct 'where' clauses for the river - $where = array(); - // river table does not have columns expected by get_access_sql_suffix so we modify its output - $where[] = str_replace("and enabled='yes'", '', - str_replace('owner_guid', 'subject_guid', get_access_sql_suffix_new('er', 'e'))); + $joins = $options['joins']; - if (empty($subject_relationship)) { - if (!empty($subject_guid)) { - if (!is_array($subject_guid)) { - $where[] = " subject_guid = {$subject_guid} "; - } else { - $where[] = " subject_guid in (" . implode(',', $subject_guid) . ") "; - } - } - } else { - if (!is_array($subject_guid)) { - $entities = elgg_get_entities_from_relationship(array( - 'relationship' => $subject_relationship, - 'relationship_guid' => $subject_guid, - 'limit' => 9999, - )); - if (is_array($entities) && !empty($entities)) { - $guids = array(); - foreach ($entities as $entity) { - $guids[] = (int) $entity->guid; - } - // $guids[] = $subject_guid; - $where[] = " subject_guid in (" . implode(',', $guids) . ") "; - } else { - return array(); - } + if ($options['relationship_guid']) { + $clauses = elgg_get_entity_relationship_where_sql( + 'rv.subject_guid', + $options['relationship'], + $options['relationship_guid'], + $options['inverse_relationship']); + if ($clauses) { + $wheres = array_merge($wheres, $clauses['wheres']); + $joins = array_merge($joins, $clauses['joins']); } } - if (!empty($object_guid)) { - if (!is_array($object_guid)) { - $where[] = " object_guid = {$object_guid} "; - } else { - $where[] = " object_guid in (" . implode(',', $object_guid) . ") "; + + // see if any functions failed + // remove empty strings on successful functions + foreach ($wheres as $i => $where) { + if ($where === FALSE) { + return FALSE; + } elseif (empty($where)) { + unset($wheres[$i]); } } - if (!empty($type)) { - $where[] = " er.type = '{$type}' "; - } - if (!empty($subtype)) { - $where[] = " er.subtype = '{$subtype}' "; - } - if (!empty($action_type)) { - $where[] = " action_type = '{$action_type}' "; + + // remove identical where clauses + $wheres = array_unique($wheres); + + if (!$options['count']) { + $query = "SELECT DISTINCT rv.* FROM {$CONFIG->dbprefix}river rv "; + } else { + $query = "SELECT count(DISTINCT rv.id) as total FROM {$CONFIG->dbprefix}river rv "; } - if (!empty($posted_min)) { - $where[] = " posted > {$posted_min} "; + + // add joins + foreach ($joins as $j) { + $query .= " $j "; } - if (!empty($posted_max)) { - $where[] = " posted < {$posted_max} "; + + // add wheres + $query .= ' WHERE '; + + foreach ($wheres as $w) { + $query .= " $w AND "; } - $whereclause = implode(' and ', $where); + $query .= elgg_river_get_access_sql(); - // Construct main SQL - $sql = "select er.*" . - " from {$CONFIG->dbprefix}river er, {$CONFIG->dbprefix}entities e " . - " where {$whereclause} AND er.object_guid = e.guid GROUP BY object_guid " . - " ORDER BY e.last_action desc LIMIT {$offset}, {$limit}"; + if (!$options['count']) { + $options['group_by'] = sanitise_string($options['group_by']); + if ($options['group_by']) { + $query .= " GROUP BY {$options['group_by']}"; + } + + $options['order_by'] = sanitise_string($options['order_by']); + $query .= " ORDER BY {$options['order_by']}"; + + if ($options['limit']) { + $limit = sanitise_int($options['limit']); + $offset = sanitise_int($options['offset'], false); + $query .= " LIMIT $offset, $limit"; + } - // Get data - return get_data($sql); + $river_items = get_data($query, 'elgg_row_to_elgg_river_item'); + _elgg_prefetch_river_entities($river_items); + + return $river_items; + } else { + $total = get_data_row($query); + return (int)$total->total; + } } /** - * Returns a human-readable representation of a river item - * - * @see get_river_items + * Prefetch entities that will be displayed in the river. * - * @param stdClass $item A river item object as returned from get_river_items - * - * @return string|false Depending on success + * @param ElggRiverItem[] $river_items + * @access private */ -function elgg_view_river_item($item) { - if (isset($item->view)) { - $object = get_entity($item->object_guid); - $subject = get_entity($item->subject_guid); - if (!$object || !$subject) { - // probably means an entity is disabled - return false; - } else { - if (elgg_view_exists($item->view)) { - $body = elgg_view($item->view, array( - 'item' => $item - )); - } +function _elgg_prefetch_river_entities(array $river_items) { + // prefetch objects and subjects + $guids = array(); + foreach ($river_items as $item) { + if ($item->subject_guid && !_elgg_retrieve_cached_entity($item->subject_guid)) { + $guids[$item->subject_guid] = true; + } + if ($item->object_guid && !_elgg_retrieve_cached_entity($item->object_guid)) { + $guids[$item->object_guid] = true; + } + } + if ($guids) { + // avoid creating oversized query + // @todo how to better handle this? + $guids = array_slice($guids, 0, 300, true); + // return value unneeded, just priming cache + elgg_get_entities(array( + 'guids' => array_keys($guids), + 'limit' => 0, + )); + } + + // prefetch object containers + $guids = array(); + foreach ($river_items as $item) { + $object = $item->getObjectEntity(); + if ($object->container_guid && !_elgg_retrieve_cached_entity($object->container_guid)) { + $guids[$object->container_guid] = true; } - return elgg_view('river/item/wrapper', array( - 'item' => $item, - 'body' => $body + } + if ($guids) { + $guids = array_slice($guids, 0, 300, true); + elgg_get_entities(array( + 'guids' => array_keys($guids), + 'limit' => 0, )); } - return false; } /** - * Returns a human-readable version of the river. + * List river items * - * @param int|array $subject_guid Acting entity to restrict to. Default: all - * @param int|array $object_guid Entity being acted on to restrict to. Default: all - * @param string $subject_relationship If set to a relationship type, this will use - * $subject_guid as the starting point and set - * the subjects to be all users this entity has this - * relationship with (eg 'friend'). Default: blank - * @param string $type The type of entity to restrict to. Default: all - * @param string $subtype The subtype of entity to restrict to. Default: all - * @param string $action_type The type of river action to restrict to. Default: all - * @param int $limit The number of items to retrieve. Default: 20 - * @param int $posted_min The minimum time period to look at. Default: none - * @param int $posted_max The maximum time period to look at. Default: none - * @param bool $pagination Show pagination? - * @param $bool $chronological Show in chronological order? + * @param array $options Any options from elgg_get_river() plus: + * pagination => BOOL Display pagination links (true) * - * @return string Human-readable river. + * @return string + * @since 1.8.0 */ -function elgg_view_river_items($subject_guid = 0, $object_guid = 0, $subject_relationship = '', -$type = '', $subtype = '', $action_type = '', $limit = 20, $posted_min = 0, -$posted_max = 0, $pagination = true, $chronological = false) { - - // Get input from outside world and sanitise it - $offset = (int) get_input('offset', 0); +function elgg_list_river(array $options = array()) { + global $autofeed; + $autofeed = true; + + $defaults = array( + 'offset' => (int) max(get_input('offset', 0), 0), + 'limit' => (int) max(get_input('limit', 20), 0), + 'pagination' => TRUE, + 'list_class' => 'elgg-list-river elgg-river', // @todo remove elgg-river in Elgg 1.9 + ); + + $options = array_merge($defaults, $options); + + if (!$options["limit"] && !$options["offset"]) {
+ // no need for pagination if listing is unlimited
+ $options["pagination"] = false;
+ } + + $options['count'] = TRUE; + $count = elgg_get_river($options); + + $options['count'] = FALSE; + $items = elgg_get_river($options); + + $options['count'] = $count; + $options['items'] = $items; + + return elgg_view('page/components/list', $options); +} - // Get the correct function - if ($chronological == true) { - $riveritems = get_river_items($subject_guid, $object_guid, $subject_relationship, $type, - $subtype, $action_type, ($limit + 1), $offset, $posted_min, $posted_max); - } else { - $riveritems = elgg_get_river_items($subject_guid, $object_guid, $subject_relationship, $type, - $subtype, $action_type, ($limit + 1), $offset, $posted_min, $posted_max); +/** + * Convert a database row to a new ElggRiverItem + * + * @param stdClass $row Database row from the river table + * + * @return ElggRiverItem + * @since 1.8.0 + * @access private + */ +function elgg_row_to_elgg_river_item($row) { + if (!($row instanceof stdClass)) { + return NULL; } - // Get river items, if they exist - if ($riveritems) { - - return elgg_view('river/item/list', array( - 'limit' => $limit, - 'offset' => $offset, - 'items' => $riveritems, - 'pagination' => $pagination - )); - - } + return new ElggRiverItem($row); +} - return ''; +/** + * Get the river's access where clause + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_river_get_access_sql() { + // rewrite default access where clause to work with river table + return str_replace("and enabled='yes'", '', + str_replace('owner_guid', 'rv.subject_guid', + str_replace('access_id', 'rv.access_id', get_access_sql_suffix()))); } /** - * This function has been added here until we decide if it is going to roll into core or not - * Add access restriction sql code to a given query. - * Note that if this code is executed in privileged mode it will return blank. + * Returns SQL where clause for type and subtype on river table * - * @TODO: DELETE once Query classes are fully integrated + * @internal This is a simplified version of elgg_get_entity_type_subtype_where_sql() + * which could be used for all queries once the subtypes have been denormalized. * - * @param string $table_prefix_one Optional table. prefix for the access code. - * @param string $table_prefix_two Another optiona table prefix? - * @param int $owner Owner GUID + * @param string $table 'rv' + * @param NULL|array $types Array of types or NULL if none. + * @param NULL|array $subtypes Array of subtypes or NULL if none + * @param NULL|array $pairs Array of pairs of types and subtypes * * @return string + * @since 1.8.0 + * @access private */ -function get_access_sql_suffix_new($table_prefix_one = '', $table_prefix_two = '', $owner = null) { - global $ENTITY_SHOW_HIDDEN_OVERRIDE, $CONFIG; - - $sql = ""; - $friends_bit = ""; - $enemies_bit = ""; - - if ($table_prefix_one) { - $table_prefix_one = sanitise_string($table_prefix_one) . "."; +function elgg_get_river_type_subtype_where_sql($table, $types, $subtypes, $pairs) { + // short circuit if nothing is requested + if (!$types && !$subtypes && !$pairs) { + return ''; } - if ($table_prefix_two) { - $table_prefix_two = sanitise_string($table_prefix_two) . "."; - } + $wheres = array(); + $types_wheres = array(); + $subtypes_wheres = array(); - if (!isset($owner)) { - $owner = get_loggedin_userid(); - } - - if (!$owner) { - $owner = -1; - } + // if no pairs, use types and subtypes + if (!is_array($pairs)) { + if ($types) { + if (!is_array($types)) { + $types = array($types); + } + foreach ($types as $type) { + $type = sanitise_string($type); + $types_wheres[] = "({$table}.type = '$type')"; + } + } - $ignore_access = elgg_check_access_overrides($owner); - $access = get_access_list($owner); + if ($subtypes) { + if (!is_array($subtypes)) { + $subtypes = array($subtypes); + } + foreach ($subtypes as $subtype) { + $subtype = sanitise_string($subtype); + $subtypes_wheres[] = "({$table}.subtype = '$subtype')"; + } + } - if ($ignore_access) { - $sql = " (1 = 1) "; - } else if ($owner != -1) { - $friends_bit = "{$table_prefix_one}access_id = " . ACCESS_FRIENDS . " - AND {$table_prefix_one}owner_guid IN ( - SELECT guid_one FROM {$CONFIG->dbprefix}entity_relationships - WHERE relationship='friend' AND guid_two=$owner - )"; + if (is_array($types_wheres) && count($types_wheres)) { + $types_wheres = array(implode(' OR ', $types_wheres)); + } - $friends_bit = '(' . $friends_bit . ') OR '; + if (is_array($subtypes_wheres) && count($subtypes_wheres)) { + $subtypes_wheres = array('(' . implode(' OR ', $subtypes_wheres) . ')'); + } - if ((isset($CONFIG->user_block_and_filter_enabled)) && ($CONFIG->user_block_and_filter_enabled)) { - // check to see if the user is in the entity owner's block list - // or if the entity owner is in the user's filter list - // if so, disallow access - $enemies_bit = get_annotation_sql('elgg_block_list', "{$table_prefix_one}owner_guid", - $owner, false); + $wheres = array(implode(' AND ', array_merge($types_wheres, $subtypes_wheres))); - $enemies_bit = '(' - . $enemies_bit - . ' AND ' . get_annotation_sql('elgg_filter_list', $owner, "{$table_prefix_one}owner_guid", - false) - . ')'; + } else { + // using type/subtype pairs + foreach ($pairs as $paired_type => $paired_subtypes) { + $paired_type = sanitise_string($paired_type); + if (is_array($paired_subtypes)) { + $paired_subtypes = array_map('sanitise_string', $paired_subtypes); + $paired_subtype_str = implode("','", $paired_subtypes); + if ($paired_subtype_str) { + $wheres[] = "({$table}.type = '$paired_type'" + . " AND {$table}.subtype IN ('$paired_subtype_str'))"; + } + } else { + $paired_subtype = sanitise_string($paired_subtypes); + $wheres[] = "({$table}.type = '$paired_type'" + . " AND {$table}.subtype = '$paired_subtype')"; + } } } - if (empty($sql)) { - $sql = " $friends_bit ({$table_prefix_one}access_id IN {$access} - OR ({$table_prefix_one}owner_guid = {$owner}) - OR ( - {$table_prefix_one}access_id = " . ACCESS_PRIVATE . " - AND {$table_prefix_one}owner_guid = $owner - ) - )"; + if (is_array($wheres) && count($wheres)) { + $where = implode(' OR ', $wheres); + return "($where)"; + } + + return ''; +} + +/** + * Get the where clause based on river action type strings + * + * @param array $types Array of action type strings + * + * @return string + * @since 1.8.0 + * @access private + */ +function elgg_river_get_action_where_sql($types) { + if (!$types) { + return ''; } - if ($enemies_bit) { - $sql = "$enemies_bit AND ($sql)"; + if (!is_array($types)) { + $types = sanitise_string($types); + return "(rv.action_type = '$types')"; } - if (!$ENTITY_SHOW_HIDDEN_OVERRIDE) { - $sql .= " and {$table_prefix_two}enabled='yes'"; + // sanitize types array + $types_sanitized = array(); + foreach ($types as $type) { + $types_sanitized[] = sanitise_string($type); } - return '(' . $sql . ')'; + $type_str = implode("','", $types_sanitized); + return "(rv.action_type IN ('$type_str'))"; } /** - * Construct and execute the query required for the activity stream. - * - * @deprecated 1.8 + * Get the where clause based on river view strings * - * @param int $limit Limit the query. - * @param int $offset Execute from the given object - * @param mixed $type A type, or array of types to look for. - * Note: This is how they appear in the SYSTEM LOG. - * @param mixed $subtype A subtype, or array of types to look for. - * Note: This is how they appear in the SYSTEM LOG. - * @param mixed $owner_guid The guid or a collection of GUIDs - * @param string $owner_relationship If defined, the relationship between $owner_guid and - * the entity owner_guid - so "is $owner_guid $owner_relationship - * with $entity->owner_guid" + * @param array $views Array of view strings * - * @return array An array of system log entries. + * @return string + * @since 1.8.0 + * @access private */ -function get_activity_stream_data($limit = 10, $offset = 0, $type = "", $subtype = "", -$owner_guid = "", $owner_relationship = "") { - - global $CONFIG; - - $limit = (int)$limit; - $offset = (int)$offset; - - if ($type) { - if (!is_array($type)) { - $type = array(sanitise_string($type)); - } else { - foreach ($type as $k => $v) { - $type[$k] = sanitise_string($v); - } - } +function elgg_river_get_view_where_sql($views) { + if (!$views) { + return ''; } - if ($subtype) { - if (!is_array($subtype)) { - $subtype = array(sanitise_string($subtype)); - } else { - foreach ($subtype as $k => $v) { - $subtype[$k] = sanitise_string($v); - } - } + if (!is_array($views)) { + $views = sanitise_string($views); + return "(rv.view = '$views')"; } - if ($owner_guid) { - if (is_array($owner_guid)) { - foreach ($owner_guid as $k => $v) { - $owner_guid[$k] = (int)$v; - } - } else { - $owner_guid = array((int)$owner_guid); - } + // sanitize views array + $views_sanitized = array(); + foreach ($views as $view) { + $views_sanitized[] = sanitise_string($view); } - $owner_relationship = sanitise_string($owner_relationship); - - // Get a list of possible views - $activity_events = array(); - $activity_views = array_merge(elgg_view_tree('activity', 'default'), - elgg_view_tree('river', 'default')); - - $done = array(); + $view_str = implode("','", $views_sanitized); + return "(rv.view IN ('$view_str'))"; +} - foreach ($activity_views as $view) { - $fragments = explode('/', $view); - $tmp = explode('/', $view, 2); - $tmp = $tmp[1]; +/** + * Sets the access ID on river items for a particular object + * + * @param int $object_guid The GUID of the entity + * @param int $access_id The access ID + * + * @return bool Depending on success + */ +function update_river_access_by_object($object_guid, $access_id) { + // Sanitise + $object_guid = (int) $object_guid; + $access_id = (int) $access_id; - if ((isset($fragments[0])) && (($fragments[0] == 'river') || ($fragments[0] == 'activity')) - && (!in_array($tmp, $done))) { + // Load config + global $CONFIG; - if (isset($fragments[1])) { - $f = array(); - for ($n = 1; $n < count($fragments); $n++) { - $val = sanitise_string($fragments[$n]); - switch($n) { - case 1: $key = 'type'; break; - case 2: $key = 'subtype'; break; - case 3: $key = 'event'; break; - } - $f[$key] = $val; - } + // Remove + $query = "update {$CONFIG->dbprefix}river + set access_id = {$access_id} + where object_guid = {$object_guid}"; + return update_data($query); +} - // Filter result based on parameters - $add = true; - if ($type) { - if (!in_array($f['type'], $type)) { - $add = false; - } - } - if (($add) && ($subtype)) { - if (!in_array($f['subtype'], $subtype)) { - $add = false; - } - } - if (($add) && ($event)) { - if (!in_array($f['event'], $event)) { - $add = false; - } - } +/** + * Page handler for activity + * + * @param array $page + * @return bool + * @access private + */ +function elgg_river_page_handler($page) { + global $CONFIG; - if ($add) { - $activity_events[] = $f; - } - } + elgg_set_page_owner_guid(elgg_get_logged_in_user_guid()); - $done[] = $tmp; - } + // make a URL segment available in page handler script + $page_type = elgg_extract(0, $page, 'all'); + $page_type = preg_replace('[\W]', '', $page_type); + if ($page_type == 'owner') { + $page_type = 'mine'; } + set_input('page_type', $page_type); - $n = 0; - foreach ($activity_events as $details) { - // Get what we're talking about - if ($details['subtype'] == 'default') { - $details['subtype'] = ''; - } - - if (($details['type']) && ($details['event'])) { - if ($n > 0) { - $obj_query .= " or "; - } - - $access = ""; - if ($details['type'] != 'relationship') { - $access = " and " . get_access_sql_suffix('sl'); - } - - $obj_query .= "( sl.object_type='{$details['type']}' - AND sl.object_subtype='{$details['subtype']}' - AND sl.event='{$details['event']}' $access )"; + require_once("{$CONFIG->path}pages/river.php"); + return true; +} - $n++; - } - } +/** + * Register river unit tests + * @access private + */ +function elgg_river_test($hook, $type, $value) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/river.php'; + return $value; +} - // User - if ((count($owner_guid)) && ($owner_guid[0] != 0)) { - $user = " and sl.performed_by_guid in (" . implode(',', $owner_guid) . ")"; - - if ($owner_relationship) { - $friendsarray = ""; - if ($friends = elgg_get_entities_from_relationship(array( - 'relationship' => $owner_relationship, - 'relationship_guid' => $owner_guid[0], - 'inverse_relationship' => FALSE, - 'types' => 'user', - 'subtypes' => $subtype, - 'limit' => 9999)) - ) { - - $friendsarray = array(); - foreach ($friends as $friend) { - $friendsarray[] = $friend->getGUID(); - } +/** + * Initialize river library + * @access private + */ +function elgg_river_init() { + elgg_register_page_handler('activity', 'elgg_river_page_handler'); + $item = new ElggMenuItem('activity', elgg_echo('activity'), 'activity'); + elgg_register_menu_item('site', $item); + + elgg_register_widget_type('river_widget', elgg_echo('river:widget:title'), elgg_echo('river:widget:description')); - $user = " and sl.performed_by_guid in (" . implode(',', $friendsarray) . ")"; - } - } - } + elgg_register_action('river/delete', '', 'admin'); - $query = "SELECT sl.* FROM {$CONFIG->dbprefix}system_log sl - WHERE 1 $user AND ($obj_query) - ORDER BY sl.time_created desc limit $offset, $limit"; - return get_data($query); + elgg_register_plugin_hook_handler('unit_test', 'system', 'elgg_river_test'); } + +elgg_register_event_handler('init', 'system', 'elgg_river_init'); diff --git a/engine/lib/sessions.php b/engine/lib/sessions.php index 05258243f..e3d5ce9cd 100644 --- a/engine/lib/sessions.php +++ b/engine/lib/sessions.php @@ -18,9 +18,9 @@ global $SESSION; * hook - 'session:get' 'user' to give plugin authors another * way to provide user details to the ACL system without touching the session. * - * @return ElggUser|NULL + * @return ElggUser */ -function get_loggedin_user() { +function elgg_get_logged_in_user_entity() { global $SESSION; if (isset($SESSION)) { @@ -33,11 +33,11 @@ function get_loggedin_user() { /** * Return the current logged in user by id. * - * @see get_loggedin_user() + * @see elgg_get_logged_in_user_entity() * @return int */ -function get_loggedin_userid() { - $user = get_loggedin_user(); +function elgg_get_logged_in_user_guid() { + $user = elgg_get_logged_in_user_entity(); if ($user) { return $user->guid; } @@ -50,9 +50,8 @@ function get_loggedin_userid() { * * @return bool */ -function isloggedin() { - - $user = get_loggedin_user(); +function elgg_is_logged_in() { + $user = elgg_get_logged_in_user_entity(); if ((isset($user)) && ($user instanceof ElggUser) && ($user->guid > 0)) { return true; @@ -64,14 +63,12 @@ function isloggedin() { /** * Returns whether or not the user is currently logged in and that they are an admin user. * - * @uses isloggedin() * @return bool */ -function isadminloggedin() { - - $user = get_loggedin_user(); +function elgg_is_admin_logged_in() { + $user = elgg_get_logged_in_user_entity(); - if ((isloggedin()) && $user->isAdmin()) { + if ((elgg_is_logged_in()) && $user->isAdmin()) { return TRUE; } @@ -90,6 +87,9 @@ function isadminloggedin() { */ function elgg_is_admin_user($user_guid) { global $CONFIG; + + $user_guid = (int)$user_guid; + // cannot use magic metadata here because of recursion // must support the old way of getting admin from metadata @@ -128,23 +128,28 @@ function elgg_is_admin_user($user_guid) { } /** - * Perform standard authentication with a given username and password. - * Returns an ElggUser object for use with login. + * Perform user authentication with a given username and password. + * + * @warning This returns an error message on failure. Use the identical operator to check + * for access: if (true === elgg_authenticate()) { ... }. + * * * @see login * - * @param string $username The username, optionally (for standard logins) - * @param string $password The password, optionally (for standard logins) + * @param string $username The username + * @param string $password The password * - * @return ElggUser|false The authenticated user object, or false on failure. + * @return true|string True or an error message on failure + * @access private */ - -function authenticate($username, $password) { - if (pam_authenticate(array('username' => $username, 'password' => $password))) { - return get_user_by_username($username); +function elgg_authenticate($username, $password) { + $pam = new ElggPAM('user'); + $credentials = array('username' => $username, 'password' => $password); + $result = $pam->authenticate($credentials); + if (!$result) { + return $pam->getFailureMessage(); } - - return false; + return true; } /** @@ -152,31 +157,34 @@ function authenticate($username, $password) { * it against a known user. * * @param array $credentials Associated array of credentials passed to - * pam_authenticate. This function expects + * Elgg's PAM system. This function expects * 'username' and 'password' (cleartext). * * @return bool + * @throws LoginException + * @access private */ -function pam_auth_userpass($credentials = NULL) { +function pam_auth_userpass(array $credentials = array()) { - if (is_array($credentials) && ($credentials['username']) && ($credentials['password'])) { - if ($user = get_user_by_username($credentials['username'])) { - // User has been banned, so prevent from logging in - if ($user->isBanned()) { - return FALSE; - } + if (!isset($credentials['username']) || !isset($credentials['password'])) { + return false; + } - if ($user->password == generate_user_password($user, $credentials['password'])) { - return TRUE; - } else { - // Password failed, log. - log_login_failure($user->guid); - } + $user = get_user_by_username($credentials['username']); + if (!$user) { + throw new LoginException(elgg_echo('LoginException:UsernameFailure')); + } - } + if (check_rate_limit_exceeded($user->guid)) { + throw new LoginException(elgg_echo('LoginException:AccountLocked')); } - return FALSE; + if ($user->password !== generate_user_password($user, $credentials['password'])) { + log_login_failure($user->guid); + throw new LoginException(elgg_echo('LoginException:PasswordFailure')); + } + + return true; } /** @@ -184,7 +192,7 @@ function pam_auth_userpass($credentials = NULL) { * * @param int $user_guid User GUID * - * @return bool on success + * @return bool */ function log_login_failure($user_guid) { $user_guid = (int)$user_guid; @@ -207,7 +215,7 @@ function log_login_failure($user_guid) { * * @param int $user_guid User GUID * - * @return bool on success (success = user has no logged failed attempts) + * @return bool true on success (success = user has no logged failed attempts) */ function reset_login_failure_count($user_guid) { $user_guid = (int)$user_guid; @@ -270,26 +278,20 @@ function check_rate_limit_exceeded($user_guid) { /** * Logs in a specified ElggUser. For standard registration, use in conjunction - * with authenticate. + * with elgg_authenticate. * - * @see authenticate + * @see elgg_authenticate * * @param ElggUser $user A valid Elgg user object * @param boolean $persistent Should this be a persistent login? * - * @return bool Whether login was successful + * @return true or throws exception + * @throws LoginException */ function login(ElggUser $user, $persistent = false) { - global $CONFIG; - // User is banned, return false. if ($user->isBanned()) { - return false; - } - - // Check rate limit - if (check_rate_limit_exceeded($user->guid)) { - return false; + throw new LoginException(elgg_echo('LoginException:BannedUser')); } $_SESSION['user'] = $user; @@ -306,7 +308,7 @@ function login(ElggUser $user, $persistent = false) { setcookie("elggperm", $code, (time() + (86400 * 30)), "/"); } - if (!$user->save() || !trigger_elgg_event('login', 'user', $user)) { + if (!$user->save() || !elgg_trigger_event('login', 'user', $user)) { unset($_SESSION['username']); unset($_SESSION['name']); unset($_SESSION['code']); @@ -314,7 +316,7 @@ function login(ElggUser $user, $persistent = false) { unset($_SESSION['id']); unset($_SESSION['user']); setcookie("elggperm", "", (time() - (86400 * 30)), "/"); - return false; + throw new LoginException(elgg_echo('LoginException:Unknown')); } // Users privilege has been elevated, so change the session id (prevents session fixation) @@ -324,6 +326,12 @@ function login(ElggUser $user, $persistent = false) { set_last_login($_SESSION['guid']); reset_login_failure_count($user->guid); // Reset any previous failed login attempts + // if memcache is enabled, invalidate the user in memcache @see https://github.com/Elgg/Elgg/issues/3143 + if (is_memcache_available()) { + // this needs to happen with a shutdown function because of the timing with set_last_login() + register_shutdown_function("_elgg_invalidate_memcache_for_entity", $_SESSION['guid']); + } + return true; } @@ -333,10 +341,8 @@ function login(ElggUser $user, $persistent = false) { * @return bool */ function logout() { - global $CONFIG; - if (isset($_SESSION['user'])) { - if (!trigger_elgg_event('logout', 'user', $_SESSION['user'])) { + if (!elgg_trigger_event('logout', 'user', $_SESSION['user'])) { return false; } $_SESSION['user']->code = ""; @@ -358,7 +364,7 @@ function logout() { session_destroy(); // starting a default session to store any post-logout messages. - session_init(NULL, NULL, NULL); + _elgg_session_boot(NULL, NULL, NULL); $_SESSION['msg'] = $old_msg; return TRUE; @@ -375,13 +381,10 @@ function logout() { * * @uses $_SESSION * - * @param string $event Event name - * @param string $object_type Object type - * @param mixed $object Object - * * @return bool + * @access private */ -function session_init($event, $object_type, $object) { +function _elgg_session_boot() { global $DB_PREFIX, $CONFIG; // Use database for sessions @@ -446,8 +449,8 @@ function session_init($event, $object_type, $object) { set_last_action($_SESSION['guid']); } - register_action("login", true); - register_action("logout"); + elgg_register_action('login', '', 'public'); + elgg_register_action('logout'); // Register a default PAM handler register_pam_handler('pam_auth_userpass'); @@ -462,9 +465,6 @@ function session_init($event, $object_type, $object) { return false; } - // Since we have loaded a new user, this user may have different language preferences - register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); - return true; } @@ -474,10 +474,10 @@ function session_init($event, $object_type, $object) { * @return void */ function gatekeeper() { - if (!isloggedin()) { + if (!elgg_is_logged_in()) { $_SESSION['last_forward_from'] = current_page_url(); register_error(elgg_echo('loggedinrequired')); - forward(); + forward('', 'login'); } } @@ -489,10 +489,10 @@ function gatekeeper() { function admin_gatekeeper() { gatekeeper(); - if (!isadminloggedin()) { + if (!elgg_is_admin_logged_in()) { $_SESSION['last_forward_from'] = current_page_url(); register_error(elgg_echo('adminrequired')); - forward(); + forward('', 'admin'); } } @@ -504,6 +504,7 @@ function admin_gatekeeper() { * * @return true * @todo Document + * @access private */ function _elgg_session_open($save_path, $session_name) { global $sess_save_path; @@ -519,6 +520,7 @@ function _elgg_session_open($save_path, $session_name) { * @todo document * * @return true + * @access private */ function _elgg_session_close() { return true; @@ -530,6 +532,7 @@ function _elgg_session_close() { * @param string $id The session ID * * @return string + * @access private */ function _elgg_session_read($id) { global $DB_PREFIX; @@ -563,6 +566,7 @@ function _elgg_session_read($id) { * @param mixed $sess_data Session data * * @return bool + * @access private */ function _elgg_session_write($id, $sess_data) { global $DB_PREFIX; @@ -602,6 +606,7 @@ function _elgg_session_write($id, $sess_data) { * @param string $id Session ID * * @return bool + * @access private */ function _elgg_session_destroy($id) { global $DB_PREFIX; @@ -616,10 +621,8 @@ function _elgg_session_destroy($id) { global $sess_save_path; $sess_file = "$sess_save_path/sess_$id"; - return(@unlink($sess_file)); + return @unlink($sess_file); } - - return false; } /** @@ -628,6 +631,7 @@ function _elgg_session_destroy($id) { * @param int $maxlifetime Max age of a session * * @return bool + * @access private */ function _elgg_session_gc($maxlifetime) { global $DB_PREFIX; @@ -650,5 +654,3 @@ function _elgg_session_gc($maxlifetime) { return true; } - -register_elgg_event_handler("boot", "system", "session_init", 20); diff --git a/engine/lib/sites.php b/engine/lib/sites.php index 960e54ee3..3de0eccc2 100644 --- a/engine/lib/sites.php +++ b/engine/lib/sites.php @@ -8,11 +8,38 @@ */ /** + * Get an ElggSite entity (default is current site) + * + * @param int $site_guid Optional. Site GUID. + * + * @return ElggSite + * @since 1.8.0 + */ +function elgg_get_site_entity($site_guid = 0) { + global $CONFIG; + + $result = false; + + if ($site_guid == 0) { + $site = $CONFIG->site; + } else { + $site = get_entity($site_guid); + } + + if ($site instanceof ElggSite) { + $result = $site; + } + + return $result; +} + +/** * Return the site specific details of a site by a row. * * @param int $guid The site GUID * * @return mixed + * @access private */ function get_site_entity_as_row($guid) { global $CONFIG; @@ -22,7 +49,7 @@ function get_site_entity_as_row($guid) { } /** - * Create or update the extras table for a given site. + * Create or update the entities table for a given site. * Call create_entity first. * * @param int $guid Site GUID @@ -31,6 +58,7 @@ function get_site_entity_as_row($guid) { * @param string $url URL of the site * * @return bool + * @access private */ function create_site_entity($guid, $name, $description, $url) { global $CONFIG; @@ -53,7 +81,7 @@ function create_site_entity($guid, $name, $description, $url) { if ($result != false) { // Update succeeded, continue $entity = get_entity($guid); - if (trigger_elgg_event('update', $entity->type, $entity)) { + if (elgg_trigger_event('update', $entity->type, $entity)) { return $guid; } else { $entity->delete(); @@ -68,7 +96,7 @@ function create_site_entity($guid, $name, $description, $url) { if ($result !== false) { $entity = get_entity($guid); - if (trigger_elgg_event('create', $entity->type, $entity)) { + if (elgg_trigger_event('create', $entity->type, $entity)) { return $guid; } else { $entity->delete(); @@ -82,24 +110,6 @@ function create_site_entity($guid, $name, $description, $url) { } /** - * THIS FUNCTION IS DEPRECATED. - * - * Delete a site's extra data. - * - * @todo remove - * - * @param int $guid Site guid - * - * @deprecated 1.5 Or so? - * @return 1 - */ -function delete_site_entity($guid) { - system_message(sprintf(elgg_echo('deprecatedfunction'), 'delete_user_entity')); - - return 1; // Always return that we have deleted one row in order to not break existing code. -} - -/** * Add a user to a site. * * @param int $site_guid Site guid @@ -108,8 +118,6 @@ function delete_site_entity($guid) { * @return bool */ function add_site_user($site_guid, $user_guid) { - global $CONFIG; - $site_guid = (int)$site_guid; $user_guid = (int)$user_guid; @@ -132,57 +140,6 @@ function remove_site_user($site_guid, $user_guid) { } /** - * Get the members of a site. - * - * @param int $site_guid Site GUID - * @param int $limit User GUID - * @param int $offset Offset - * - * @return mixed - */ -function get_site_members($site_guid, $limit = 10, $offset = 0) { - $site_guid = (int)$site_guid; - $limit = (int)$limit; - $offset = (int)$offset; - - return elgg_get_entities_from_relationship(array( - 'relationship' => 'member_of_site', - 'relationship_guid' => $site_guid, - 'inverse_relationship' => TRUE, - 'types' => 'user', - 'limit' => $limit, 'offset' => $offset - )); -} - -/** - * Display a list of site members - * - * @param int $site_guid The GUID of the site - * @param int $limit The number of members to display on a page - * @param bool $fullview Whether or not to display the full view (default: true) - * - * @return string A displayable list of members - */ -function list_site_members($site_guid, $limit = 10, $fullview = true) { - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $options = array( - 'relationship' => 'member_of_site', - 'relationship_guid' => $site_guid, - 'inverse_relationship' => TRUE, - 'types' => 'user', - 'limit' => $limit, - 'offset' => $offset, - 'count' => TRUE - ); - $count = (int) elgg_get_entities_from_relationship($options); - $entities = get_site_members($site_guid, $limit, $offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview); - -} - -/** * Add an object to a site. * * @param int $site_guid Site GUID @@ -191,8 +148,6 @@ function list_site_members($site_guid, $limit = 10, $fullview = true) { * @return mixed */ function add_site_object($site_guid, $object_guid) { - global $CONFIG; - $site_guid = (int)$site_guid; $object_guid = (int)$object_guid; @@ -226,77 +181,15 @@ function remove_site_object($site_guid, $object_guid) { */ function get_site_objects($site_guid, $subtype = "", $limit = 10, $offset = 0) { $site_guid = (int)$site_guid; - $subtype = sanitise_string($subtype); - $limit = (int)$limit; - $offset = (int)$offset; - - return elgg_get_entities_from_relationship(array( - 'relationship' => 'member_of_site', - 'relationship_guid' => $site_guid, - 'inverse_relationship' => TRUE, - 'types' => 'object', - 'subtypes' => $subtype, - 'limit' => $limit, - 'offset' => $offset - )); -} - -/** - * Add a collection to a site. - * - * @param int $site_guid Site GUID - * @param int $collection_guid Collection GUID - * - * @return mixed - */ -function add_site_collection($site_guid, $collection_guid) { - global $CONFIG; - - $site_guid = (int)$site_guid; - $collection_guid = (int)$collection_guid; - - return add_entity_relationship($collection_guid, "member_of_site", $site_guid); -} - -/** - * Remove a collection from a site. - * - * @param int $site_guid Site GUID - * @param int $collection_guid Collection GUID - * - * @todo probably remove. - * @return mixed - */ -function remove_site_collection($site_guid, $collection_guid) { - $site_guid = (int)$site_guid; - $collection_guid = (int)$collection_guid; - - return remove_entity_relationship($collection_guid, "member_of_site", $site_guid); -} - -/** - * Get the collections belonging to a site. - * - * @param int $site_guid Site GUID - * @param string $subtype Subtype - * @param int $limit Limit - * @param int $offset Offset - * - * @return mixed - */ -function get_site_collections($site_guid, $subtype = "", $limit = 10, $offset = 0) { - $site_guid = (int)$site_guid; - $subtype = sanitise_string($subtype); $limit = (int)$limit; $offset = (int)$offset; - // collection isn't a valid type. This won't work. return elgg_get_entities_from_relationship(array( 'relationship' => 'member_of_site', 'relationship_guid' => $site_guid, 'inverse_relationship' => TRUE, - 'types' => 'collection', - 'subtypes' => $subtype, + 'type' => 'object', + 'subtype' => $subtype, 'limit' => $limit, 'offset' => $offset )); @@ -317,65 +210,13 @@ function get_site_by_url($url) { $row = get_data_row("SELECT * from {$CONFIG->dbprefix}sites_entity where url='$url'"); if ($row) { - return new ElggSite($row); + return get_entity($row->guid); } return false; } /** - * Searches for a site based on a complete or partial name - * or description or url using full text searching. - * - * IMPORTANT NOTE: With MySQL's default setup: - * 1) $criteria must be 4 or more characters long - * 2) If $criteria matches greater than 50% of results NO RESULTS ARE RETURNED! - * - * @param string $criteria The partial or full name or username. - * @param int $limit Limit of the search. - * @param int $offset Offset. - * @param string $order_by The order. - * @param boolean $count Whether to return the count of results or just the results. - * - * @return mixed - * @deprecated 1.7 - */ -function search_for_site($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { - elgg_deprecated_notice('search_for_site() was deprecated by new search plugin.', 1.7); - global $CONFIG; - - $criteria = sanitise_string($criteria); - $limit = (int)$limit; - $offset = (int)$offset; - $order_by = sanitise_string($order_by); - - $access = get_access_sql_suffix("e"); - - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - - if ($count) { - $query = "SELECT count(e.guid) as total "; - } else { - $query = "SELECT e.* "; - } - $query .= "from {$CONFIG->dbprefix}entities e - join {$CONFIG->dbprefix}sites_entity s on e.guid=s.guid - where match(s.name, s.description, s.url) against ('$criteria') and $access"; - - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; // Add order and limit - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } - } - return false; -} - -/** * Retrieve a site and return the domain portion of its url. * * @param int $guid ElggSite GUID @@ -395,53 +236,21 @@ function get_site_domain($guid) { } /** - * Initialise site handling - * - * Called at the beginning of system running, to set the ID of the current site. - * This is 0 by default, but plugins may alter this behaviour by attaching functions - * to the sites init event and changing $CONFIG->site_id. - * - * @uses $CONFIG - * - * @param string $event Event API required parameter - * @param string $object_type Event API required parameter - * @param null $object Event API required parameter - * - * @return true - */ -function sites_boot($event, $object_type, $object) { - global $CONFIG; - - $site = trigger_plugin_hook("siteid", "system"); - if ($site === null || $site === false) { - $CONFIG->site_id = (int) datalist_get('default_site'); - } else { - $CONFIG->site_id = $site; - } - $CONFIG->site_guid = $CONFIG->site_id; - $CONFIG->site = get_entity($CONFIG->site_guid); - - return true; -} - -// Register event handlers -register_elgg_event_handler('boot', 'system', 'sites_boot', 2); - -// Register with unit test -register_plugin_hook('unit_test', 'system', 'sites_test'); - -/** * Unit tests for sites * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params * * @return array + * @access private */ function sites_test($hook, $type, $value, $params) { global $CONFIG; $value[] = "{$CONFIG->path}engine/tests/objects/sites.php"; return $value; } + +// Register with unit test +elgg_register_plugin_hook_handler('unit_test', 'system', 'sites_test'); diff --git a/engine/lib/statistics.php b/engine/lib/statistics.php index 0067f3672..4cb0bb0b8 100644 --- a/engine/lib/statistics.php +++ b/engine/lib/statistics.php @@ -95,28 +95,32 @@ function get_number_users($show_deactivated = false) { * @return string */ function get_online_users() { - $offset = get_input('offset', 0); - $count = count(find_active_users(600, 9999)); - $objects = find_active_users(600, 10, $offset); + $limit = max(0, (int) get_input("limit", 10)); + $offset = max(0, (int) get_input("offset", 0)); + + $count = find_active_users(600, $limit, $offset, true); + $objects = find_active_users(600, $limit, $offset); if ($objects) { - return elgg_view_entity_list($objects, $count, $offset, 10, false); + return elgg_view_entity_list($objects, array( + 'count' => $count, + 'limit' => $limit, + 'offset' => $offset + )); } + return ''; } /** * Initialise the statistics admin page. * * @return void + * @access private */ function statistics_init() { - extend_elgg_admin_page('admin/statistics_opt/basic', 'admin/statistics'); - extend_elgg_admin_page('admin/statistics_opt/numentities', 'admin/statistics'); - extend_elgg_admin_page('admin/statistics_opt/online', 'admin/statistics'); - - extend_elgg_settings_page('usersettings/statistics_opt/online', 'usersettings/statistics'); - extend_elgg_settings_page('usersettings/statistics_opt/numentities', 'usersettings/statistics'); + elgg_extend_view('core/settings/statistics', 'core/settings/statistics/online'); + elgg_extend_view('core/settings/statistics', 'core/settings/statistics/numentities'); } /// Register init function -register_elgg_event_handler('init', 'system', 'statistics_init');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'statistics_init'); diff --git a/engine/lib/system_log.php b/engine/lib/system_log.php index 05e3985eb..84302632e 100644 --- a/engine/lib/system_log.php +++ b/engine/lib/system_log.php @@ -10,7 +10,10 @@ /** * Retrieve the system log based on a number of parameters. * + * @todo too many args, and the first arg is too confusing + * * @param int|array $by_user The guid(s) of the user(s) who initiated the event. + * Use 0 for unowned entries. Anything else falsey means anyone. * @param string $event The event you are searching on. * @param string $class The class of object it effects. * @param string $type The type @@ -21,11 +24,12 @@ * @param int $timebefore Lower time limit * @param int $timeafter Upper time limit * @param int $object_id GUID of an object - * + * @param string $ip_address The IP address. * @return mixed */ -function get_system_log($by_user = "", $event = "", $class = "", $type = "", $subtype = "", -$limit = 10, $offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $object_id = 0) { +function get_system_log($by_user = "", $event = "", $class = "", $type = "", $subtype = "", $limit = 10, + $offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $object_id = 0, + $ip_address = "") { global $CONFIG; @@ -37,16 +41,18 @@ $limit = 10, $offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $obje } else { $by_user = (int)$by_user; } + $event = sanitise_string($event); $class = sanitise_string($class); $type = sanitise_string($type); $subtype = sanitise_string($subtype); + $ip_address = sanitise_string($ip_address); $limit = (int)$limit; $offset = (int)$offset; $where = array(); - if ($by_user_orig !== "") { + if ($by_user_orig !== "" && $by_user_orig !== false && $by_user_orig !== null) { if (is_int($by_user)) { $where[] = "performed_by_guid=$by_user"; } else if (is_array($by_user)) { @@ -75,6 +81,9 @@ $limit = 10, $offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $obje if ($object_id) { $where[] = "object_id = " . ((int) $object_id); } + if ($ip_address) { + $where[] = "ip_address = '$ip_address'"; + } $select = "*"; if ($count) { @@ -91,7 +100,8 @@ $limit = 10, $offset = 0, $count = false, $timebefore = 0, $timeafter = 0, $obje } if ($count) { - if ($numrows = get_data_row($query)) { + $numrows = get_data_row($query); + if ($numrows) { return $numrows->count; } } else { @@ -128,9 +138,12 @@ function get_object_from_log_entry($entry_id) { if ($entry) { $class = $entry->object_class; - $tmp = new $class(); - $object = $tmp->getObjectFromID($entry->object_id); - + // surround with try/catch because object could be disabled + try { + $object = new $class($entry->object_id); + } catch (Exception $e) { + + } if ($object) { return $object; } @@ -145,17 +158,26 @@ function get_object_from_log_entry($entry_id) { * This is called by the event system and should not be called directly. * * @param object $object The object you're talking about. - * @param string $event String The event being logged - * - * @return mixed + * @param string $event The event being logged + * @return void */ function system_log($object, $event) { global $CONFIG; - static $logcache; + static $log_cache; + static $cache_size = 0; if ($object instanceof Loggable) { - if (!is_array($logcache)) { - $logcache = array(); + + /* @var ElggEntity|ElggExtender $object */ + if (datalist_get('version') < 2012012000) { + // this is a site that doesn't have the ip_address column yet + return; + } + + // reset cache if it has grown too large + if (!is_array($log_cache) || $cache_size > 500) { + $log_cache = array(); + $cache_size = 0; } // Has loggable interface, extract the necessary information and store @@ -165,7 +187,17 @@ function system_log($object, $event) { $object_subtype = $object->getSubtype(); $event = sanitise_string($event); $time = time(); - $performed_by = get_loggedin_userid(); + + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ip_address = array_pop(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])); + } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) { + $ip_address = array_pop(explode(',', $_SERVER['HTTP_X_REAL_IP'])); + } else { + $ip_address = $_SERVER['REMOTE_ADDR']; + } + $ip_address = sanitise_string($ip_address); + + $performed_by = elgg_get_logged_in_user_guid(); if (isset($object->access_id)) { $access_id = $object->access_id; @@ -185,20 +217,19 @@ function system_log($object, $event) { } // Create log if we haven't already created it - if (!isset($logcache[$time][$object_id][$event])) { + if (!isset($log_cache[$time][$object_id][$event])) { $query = "INSERT DELAYED into {$CONFIG->dbprefix}system_log (object_id, object_class, object_type, object_subtype, event, - performed_by_guid, owner_guid, access_id, enabled, time_created) + performed_by_guid, owner_guid, access_id, enabled, time_created, ip_address) VALUES ('$object_id','$object_class','$object_type', '$object_subtype', '$event', - $performed_by, $owner_guid, $access_id, '$enabled', '$time')"; + $performed_by, $owner_guid, $access_id, '$enabled', '$time', '$ip_address')"; insert_data($query); - $logcache[$time][$object_id][$event] = true; + $log_cache[$time][$object_id][$event] = true; + $cache_size += 1; } - - return true; } } @@ -263,17 +294,18 @@ function system_log_default_logger($event, $object_type, $object) { * @param Loggable $object Object to log * * @return true + * @access private */ function system_log_listener($event, $object_type, $object) { if (($object_type != 'systemlog') && ($event != 'log')) { - trigger_elgg_event('log', 'systemlog', array('object' => $object, 'event' => $event)); + elgg_trigger_event('log', 'systemlog', array('object' => $object, 'event' => $event)); } return true; } /** Register event to listen to all events **/ -register_elgg_event_handler('all', 'all', 'system_log_listener', 400); +elgg_register_event_handler('all', 'all', 'system_log_listener', 400); /** Register a default system log handler */ -register_elgg_event_handler('log', 'systemlog', 'system_log_default_logger', 999);
\ No newline at end of file +elgg_register_event_handler('log', 'systemlog', 'system_log_default_logger', 999); diff --git a/engine/lib/tags.php b/engine/lib/tags.php index 68cfee940..586a9b9e4 100644 --- a/engine/lib/tags.php +++ b/engine/lib/tags.php @@ -17,6 +17,7 @@ * @param int $buckets The number of buckets * * @return int + * @access private */ function calculate_tag_size($min, $max, $number_of_tags, $buckets = 6) { $delta = (($max - $min) / $buckets); @@ -47,7 +48,8 @@ function calculate_tag_size($min, $max, $number_of_tags, $buckets = 6) { * @param array $tags The array of tags. * @param int $buckets The number of buckets * - * @return An associated array of tags with a weighting, this can then be mapped to a display class. + * @return array An associated array of tags with a weighting, this can then be mapped to a display class. + * @access private */ function generate_tag_cloud(array $tags, $buckets = 6) { $cloud = array(); @@ -112,8 +114,8 @@ function generate_tag_cloud(array $tags, $buckets = 6) { * * joins => array() Additional joins * - * @return false/array - if no tags or error, false - * otherwise, array of objects with ->tag and ->total values + * @return object[]|false If no tags or error, false + * otherwise, array of objects with ->tag and ->total values * @since 1.7.1 */ function elgg_get_tags(array $options = array()) { @@ -144,10 +146,9 @@ function elgg_get_tags(array $options = array()) { $options = array_merge($defaults, $options); - $singulars = array('type', 'subtype', 'owner_guid', 'container_guid', 'site_guid'); + $singulars = array('type', 'subtype', 'owner_guid', 'container_guid', 'site_guid', 'tag_name'); $options = elgg_normalise_plural_options_array($options, $singulars); - $registered_tags = elgg_get_registered_tag_metadata_names(); if (!is_array($options['tag_names'])) { @@ -171,6 +172,7 @@ function elgg_get_tags(array $options = array()) { // catch for tags that were spaces $wheres[] = "msv.string != ''"; + $sanitised_tags = array(); foreach ($options['tag_names'] as $tag) { $sanitised_tags[] = '"' . sanitise_string($tag) . '"'; } @@ -179,15 +181,12 @@ function elgg_get_tags(array $options = array()) { $wheres[] = elgg_get_entity_type_subtype_where_sql('e', $options['types'], $options['subtypes'], $options['type_subtype_pairs']); - $wheres[] = elgg_get_entity_site_where_sql('e', $options['site_guids']); - $wheres[] = elgg_get_entity_owner_where_sql('e', $options['owner_guids']); - $wheres[] = elgg_get_entity_container_where_sql('e', $options['container_guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.site_guid', $options['site_guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.owner_guid', $options['owner_guids']); + $wheres[] = elgg_get_guid_based_where_sql('e.container_guid', $options['container_guids']); $wheres[] = elgg_get_entity_time_where_sql('e', $options['created_time_upper'], $options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']); - // remove identical where clauses - $wheres = array_unique($wheres); - // see if any functions failed // remove empty strings on successful functions foreach ($wheres as $i => $where) { @@ -198,6 +197,8 @@ function elgg_get_tags(array $options = array()) { } } + // remove identical where clauses + $wheres = array_unique($wheres); $joins = $options['joins']; @@ -246,75 +247,6 @@ function elgg_get_tags(array $options = array()) { } /** - * Get an array of tags with weights for use with the output/tagcloud view. - * - * @deprecated 1.8 Use elgg_get_tags(). - * - * @param int $threshold Get the threshold of minimum number of each tags to - * bother with (ie only show tags where there are more - * than $threshold occurances) - * @param int $limit Number of tags to return - * @param string $metadata_name Optionally, the name of the field you want to grab for - * @param string $entity_type Optionally, the entity type ('object' etc) - * @param string $entity_subtype The entity subtype, optionally - * @param int $owner_guid The GUID of the tags owner, optionally - * @param int $site_guid Optionally, the site to restrict to (default is the current site) - * @param int $start_ts Optionally specify a start timestamp for tags used to - * generate cloud. - * @param int $end_ts Optionally specify an end timestamp for tags used to generate cloud - * - * @return array|false Array of objects with ->tag and ->total values, or false on failure - */ -function get_tags($threshold = 1, $limit = 10, $metadata_name = "", $entity_type = "object", -$entity_subtype = "", $owner_guid = "", $site_guid = -1, $start_ts = "", $end_ts = "") { - - elgg_deprecated_notice('get_tags() has been replaced by elgg_get_tags()', 1.8); - - if (is_array($metadata_name)) { - return false; - } - - $options = array(); - if ($metadata_name === '') { - $options['tag_names'] = array(); - } else { - $options['tag_names'] = array($metadata_name); - } - - $options['threshold'] = $threshold; - $options['limit'] = $limit; - - // rewrite owner_guid to container_guid to emulate old functionality - $container_guid = $owner_guid; - if ($container_guid) { - $options['container_guids'] = $container_guid; - } - - if ($entity_type) { - $options['type'] = $entity_type; - } - - if ($entity_subtype) { - $options['subtype'] = $entity_subtype; - } - - if ($site_guid != -1) { - $options['site_guids'] = $site_guid; - } - - if ($end_ts) { - $options['created_time_upper'] = $end_ts; - } - - if ($start_ts) { - $options['created_time_lower'] = $start_ts; - } - - $r = elgg_get_tags($options); - return $r; -} - -/** * Returns viewable tagcloud * * @see elgg_get_tags @@ -339,45 +271,10 @@ function elgg_view_tagcloud(array $options = array()) { } $tag_data = elgg_get_tags($options); - return elgg_view("output/tagcloud", array('value' => $tag_data, - 'type' => $type, - 'subtype' => $subtype)); - -} - -/** - * Loads and displays a tagcloud given particular criteria. - * - * @deprecated 1.8 use elgg_view_tagcloud() - * - * @param int $threshold Get the threshold of minimum number of each tags - * to bother with (ie only show tags where there are - * more than $threshold occurances) - * @param int $limit Number of tags to return - * @param string $metadata_name Optionally, the name of the field you want to grab for - * @param string $entity_type Optionally, the entity type ('object' etc) - * @param string $entity_subtype The entity subtype, optionally - * @param int $owner_guid The GUID of the tags owner, optionally - * @param int $site_guid Optionally, the site to restrict to (default is the current site) - * @param int $start_ts Optionally specify a start timestamp for tags used to - * generate cloud. - * @param int $end_ts Optionally specify an end timestamp for tags used to generate - * cloud. - * - * @return string The HTML (or other, depending on view type) of the tagcloud. - */ -function display_tagcloud($threshold = 1, $limit = 10, $metadata_name = "", $entity_type = "object", -$entity_subtype = "", $owner_guid = "", $site_guid = -1, $start_ts = "", $end_ts = "") { - - elgg_deprecated_notice('display_cloud() was deprecated by elgg_view_tagcloud()!', 1.8); - - $tags = get_tags($threshold, $limit, $metadata_name, $entity_type, - $entity_subtype, $owner_guid, $site_guid, $start_ts, $end_ts); - - return elgg_view('output/tagcloud', array( - 'value' => $tags, - 'type' => $entity_type, - 'subtype' => $entity_subtype, + return elgg_view("output/tagcloud", array( + 'value' => $tag_data, + 'type' => $type, + 'subtype' => $subtype, )); } @@ -420,28 +317,38 @@ function elgg_get_registered_tag_metadata_names() { return $names; } -// register the standard tags metadata name -elgg_register_tag_metadata_name('tags'); - -register_page_handler('tags', 'elgg_tagcloud_page_handler'); - /** * Page hander for tags * * @param array $page Page array * - * @return void + * @return bool + * @access private */ function elgg_tagcloud_page_handler($page) { - global $CONFIG; - switch ($page[0]) { - default: - $title = elgg_view_title(elgg_echo('tags:site_cloud')); - $tags = display_tagcloud(0, 100, 'tags'); - $body = elgg_view_layout('one_column_with_sidebar', $title . $tags); + $title = elgg_view_title(elgg_echo('tags:site_cloud')); + $options = array( + 'threshold' => 0, + 'limit' => 100, + 'tag_name' => 'tags', + ); + $tags = elgg_view_tagcloud($options); + $content = $title . $tags; + $body = elgg_view_layout('one_sidebar', array('content' => $content)); + + echo elgg_view_page(elgg_echo('tags:site_cloud'), $body); + return true; +} - echo elgg_view_page(elgg_echo('tags:site_cloud'), $body); - break; - } +/** + * @access private + */ +function elgg_tags_init() { + // register the standard tags metadata name + elgg_register_tag_metadata_name('tags'); + + elgg_register_page_handler('tags', 'elgg_tagcloud_page_handler'); } + +elgg_register_event_handler('init', 'system', 'elgg_tags_init');
\ No newline at end of file diff --git a/engine/lib/upgrade.php b/engine/lib/upgrade.php new file mode 100644 index 000000000..158ec9ec1 --- /dev/null +++ b/engine/lib/upgrade.php @@ -0,0 +1,365 @@ +<?php +/** + * Elgg upgrade library. + * Contains code for handling versioning and upgrades. + * + * @package Elgg.Core + * @subpackage Upgrade + */ + +/** + * Run any php upgrade scripts which are required + * + * @param int $version Version upgrading from. + * @param bool $quiet Suppress errors. Don't use this. + * + * @return bool + * @access private + */ +function upgrade_code($version, $quiet = FALSE) { + // do not remove - upgrade scripts depend on this + global $CONFIG; + + $version = (int) $version; + $upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/'; + $processed_upgrades = elgg_get_processed_upgrades(); + + // upgrading from 1.7 to 1.8. Need to bootstrap. + if (!$processed_upgrades) { + elgg_upgrade_bootstrap_17_to_18(); + + // grab accurate processed upgrades + $processed_upgrades = elgg_get_processed_upgrades(); + } + + $upgrade_files = elgg_get_upgrade_files($upgrade_path); + + if ($upgrade_files === false) { + return false; + } + + $upgrades = elgg_get_unprocessed_upgrades($upgrade_files, $processed_upgrades); + + // Sort and execute + sort($upgrades); + + foreach ($upgrades as $upgrade) { + $upgrade_version = elgg_get_upgrade_file_version($upgrade); + $success = true; + + // hide all errors. + if ($quiet) { + // hide include errors as well as any exceptions that might happen + try { + if (!@include("$upgrade_path/$upgrade")) { + $success = false; + error_log("Could not include $upgrade_path/$upgrade"); + } + } catch (Exception $e) { + $success = false; + error_log($e->getmessage()); + } + } else { + if (!include("$upgrade_path/$upgrade")) { + $success = false; + error_log("Could not include $upgrade_path/$upgrade"); + } + } + + if ($success) { + // incrementally set upgrade so we know where to start if something fails. + $processed_upgrades[] = $upgrade; + + // don't set the version to a lower number in instances where an upgrade + // has been merged from a lower version of Elgg + if ($upgrade_version > $version) { + datalist_set('version', $upgrade_version); + } + + elgg_set_processed_upgrades($processed_upgrades); + } else { + return false; + } + } + + return true; +} + +/** + * Saves the processed upgrades to a dataset. + * + * @param array $processed_upgrades An array of processed upgrade filenames + * (not the path, just the file) + * @return bool + * @access private + */ +function elgg_set_processed_upgrades(array $processed_upgrades) { + $processed_upgrades = array_unique($processed_upgrades); + return datalist_set('processed_upgrades', serialize($processed_upgrades)); +} + +/** + * Gets a list of processes upgrades + * + * @return mixed Array of processed upgrade filenames or false + * @access private + */ +function elgg_get_processed_upgrades() { + $upgrades = datalist_get('processed_upgrades'); + $unserialized = unserialize($upgrades); + return $unserialized; +} + +/** + * Returns the version of the upgrade filename. + * + * @param string $filename The upgrade filename. No full path. + * @return int|false + * @since 1.8.0 + * @access private + */ +function elgg_get_upgrade_file_version($filename) { + preg_match('/^([0-9]{10})([\.a-z0-9-_]+)?\.(php)$/i', $filename, $matches); + + if (isset($matches[1])) { + return (int) $matches[1]; + } + + return false; +} + +/** + * Returns a list of upgrade files relative to the $upgrade_path dir. + * + * @param string $upgrade_path The up + * @return array|false + * @access private + */ +function elgg_get_upgrade_files($upgrade_path = null) { + if (!$upgrade_path) { + $upgrade_path = elgg_get_config('path') . 'engine/lib/upgrades/'; + } + $upgrade_path = sanitise_filepath($upgrade_path); + $handle = opendir($upgrade_path); + + if (!$handle) { + return false; + } + + $upgrade_files = array(); + + while ($upgrade_file = readdir($handle)) { + // make sure this is a wellformed upgrade. + if (is_dir($upgrade_path . '$upgrade_file')) { + continue; + } + $upgrade_version = elgg_get_upgrade_file_version($upgrade_file); + if (!$upgrade_version) { + continue; + } + $upgrade_files[] = $upgrade_file; + } + + sort($upgrade_files); + + return $upgrade_files; +} + +/** + * Get the current Elgg version information + * + * @param bool $humanreadable Whether to return a human readable version (default: false) + * + * @return string|false Depending on success + */ +function get_version($humanreadable = false) { + global $CONFIG; + + static $version, $release; + + if (isset($CONFIG->path)) { + if (!isset($version) || !isset($release)) { + if (!include($CONFIG->path . "version.php")) { + return false; + } + } + return (!$humanreadable) ? $version : $release; + } + + return false; +} + +/** + * Checks if any upgrades need to be run. + * + * @param null|array $upgrade_files Optional upgrade files + * @param null|array $processed_upgrades Optional processed upgrades + * + * @return array + * @access private + */ +function elgg_get_unprocessed_upgrades($upgrade_files = null, $processed_upgrades = null) { + if ($upgrade_files === null) { + $upgrade_files = elgg_get_upgrade_files(); + } + + if ($processed_upgrades === null) { + $processed_upgrades = unserialize(datalist_get('processed_upgrades')); + if (!is_array($processed_upgrades)) { + $processed_upgrades = array(); + } + } + + $unprocessed = array_diff($upgrade_files, $processed_upgrades); + return $unprocessed; +} + +/** + * Determines whether or not the database needs to be upgraded. + * + * @return bool Depending on whether or not the db version matches the code version + * @access private + */ +function version_upgrade_check() { + $dbversion = (int) datalist_get('version'); + $version = get_version(); + + if ($version > $dbversion) { + return TRUE; + } + + return FALSE; +} + +/** + * Upgrades Elgg Database and code + * + * @return bool + * @access private + */ +function version_upgrade() { + // It's possible large upgrades could exceed the max execution time. + set_time_limit(0); + + $dbversion = (int) datalist_get('version'); + + // No version number? Oh snap...this is an upgrade from a clean installation < 1.7. + // Run all upgrades without error reporting and hope for the best. + // See https://github.com/elgg/elgg/issues/1432 for more. + $quiet = !$dbversion; + + // Note: Database upgrades are deprecated as of 1.8. Use code upgrades. See #1433 + if (db_upgrade($dbversion, '', $quiet)) { + system_message(elgg_echo('upgrade:db')); + } + + if (upgrade_code($dbversion, $quiet)) { + system_message(elgg_echo('upgrade:core')); + + // Now we trigger an event to give the option for plugins to do something + $upgrade_details = new stdClass; + $upgrade_details->from = $dbversion; + $upgrade_details->to = get_version(); + + elgg_trigger_event('upgrade', 'upgrade', $upgrade_details); + + return true; + } + + return false; +} + +/** + * Boot straps into 1.8 upgrade system from 1.7 + * + * This runs all the 1.7 upgrades, then sets the processed_upgrades to all existing 1.7 upgrades. + * Control is then passed back to the main upgrade function which detects and runs the + * 1.8 upgrades, regardless of filename convention. + * + * @return bool + * @access private + */ +function elgg_upgrade_bootstrap_17_to_18() { + $db_version = (int) datalist_get('version'); + + // the 1.8 upgrades before the upgrade system change that are interspersed with 1.7 upgrades. + $upgrades_18 = array( + '2010111501.php', + '2010121601.php', + '2010121602.php', + '2010121701.php', + '2010123101.php', + '2011010101.php', + ); + + $upgrade_files = elgg_get_upgrade_files(); + $processed_upgrades = array(); + + foreach ($upgrade_files as $upgrade_file) { + // ignore if not in 1.7 format or if it's a 1.8 upgrade + if (in_array($upgrade_file, $upgrades_18) || !preg_match("/[0-9]{10}\.php/", $upgrade_file)) { + continue; + } + + $upgrade_version = elgg_get_upgrade_file_version($upgrade_file); + + // this has already been run in a previous 1.7.X -> 1.7.X upgrade + if ($upgrade_version < $db_version) { + $processed_upgrades[] = $upgrade_file; + } + } + + return elgg_set_processed_upgrades($processed_upgrades); +} + +/** + * Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades. + * + * @see _elgg_upgrade_lock() + * + * @return bool + * @access private + */ +function _elgg_upgrade_lock() { + global $CONFIG; + + if (!_elgg_upgrade_is_locked()) { + // lock it + insert_data("create table {$CONFIG->dbprefix}upgrade_lock (id INT)"); + elgg_log('Locked for upgrade.', 'NOTICE'); + return true; + } + + elgg_log('Cannot lock for upgrade: already locked.', 'WARNING'); + return false; +} + +/** + * Unlocks upgrade. + * + * @see _elgg_upgrade_lock() + * + * @access private + */ +function _elgg_upgrade_unlock() { + global $CONFIG; + delete_data("drop table {$CONFIG->dbprefix}upgrade_lock"); + elgg_log('Upgrade unlocked.', 'NOTICE'); +} + +/** + * Checks if upgrade is locked + * + * @return bool + * @access private + */ +function _elgg_upgrade_is_locked() { + global $CONFIG; + + $is_locked = count(get_data("show tables like '{$CONFIG->dbprefix}upgrade_lock'")); + + // @todo why? + _elgg_invalidate_query_cache(); + + return $is_locked; +} diff --git a/engine/lib/upgrades/2008100701.php b/engine/lib/upgrades/2008100701.php index 394a90046..b8d4dfdbc 100644 --- a/engine/lib/upgrades/2008100701.php +++ b/engine/lib/upgrades/2008100701.php @@ -1,7 +1,7 @@ <?php - /// Activate mail plugin - /** - * Because Elgg now has a plugable account activation process we need to activate - * the email account activation plugin for existing installs. - */ - enable_plugin('uservalidationbyemail', $CONFIG->site->guid);
\ No newline at end of file + +/** + * Because Elgg now has a plugable account activation process we need to activate + * the email account activation plugin for existing installs. + */ +enable_plugin('uservalidationbyemail', $CONFIG->site->guid); diff --git a/engine/lib/upgrades/2009022701.php b/engine/lib/upgrades/2009022701.php index 2e83b56b3..54083a34d 100644 --- a/engine/lib/upgrades/2009022701.php +++ b/engine/lib/upgrades/2009022701.php @@ -1,7 +1,7 @@ <?php - global $CONFIG; +global $CONFIG; - /** - * Disable update client since this has now been removed. - */ - disable_plugin('updateclient', $CONFIG->site->guid);
\ No newline at end of file +/** + * Disable update client since this has now been removed. + */ +disable_plugin('updateclient', $CONFIG->site->guid); diff --git a/engine/lib/upgrades/2009041701.php b/engine/lib/upgrades/2009041701.php index acc8fc0bd..7b31a3bc9 100644 --- a/engine/lib/upgrades/2009041701.php +++ b/engine/lib/upgrades/2009041701.php @@ -1,9 +1,8 @@ <?php - global $CONFIG; +global $CONFIG; - /// Activate kses - /** - * Elgg now has kses tag filtering built as a plugin. This needs to be enabled. - */ - enable_plugin('kses', $CONFIG->site->guid);
\ No newline at end of file +/** + * Elgg now has kses tag filtering built as a plugin. This needs to be enabled. + */ +enable_plugin('kses', $CONFIG->site->guid); diff --git a/engine/lib/upgrades/2009070101.php b/engine/lib/upgrades/2009070101.php index 3bf89e9b7..d0eae9b91 100644 --- a/engine/lib/upgrades/2009070101.php +++ b/engine/lib/upgrades/2009070101.php @@ -1,10 +1,9 @@ <?php - global $CONFIG; +global $CONFIG; - /// Deprecate kses and activate htmlawed - /** - * Kses appears to be a dead project so we are deprecating it in favour of htmlawed. - */ - disable_plugin('kses', $CONFIG->site->guid); - enable_plugin('htmlawed', $CONFIG->site->guid);
\ No newline at end of file +/** + * Kses appears to be a dead project so we are deprecating it in favour of htmlawed. + */ +disable_plugin('kses', $CONFIG->site->guid); +enable_plugin('htmlawed', $CONFIG->site->guid); diff --git a/engine/lib/upgrades/2009102801.php b/engine/lib/upgrades/2009102801.php index 8885dbb09..3ad113fb2 100644 --- a/engine/lib/upgrades/2009102801.php +++ b/engine/lib/upgrades/2009102801.php @@ -1,7 +1,8 @@ <?php -// disable timeout for large sites. -set_time_limit(0); +/** + * Move user's data directories from using username to registration date + */ /** * Generates a file matrix like Elgg 1.0 did @@ -202,14 +203,15 @@ function user_file_matrix($guid) { return "$time_created/$user->guid/"; } -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE; +global $ENTITY_CACHE, $CONFIG; /** - Upgrade file locations + * Upgrade file locations */ $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity WHERE username != ''"); while ($user = mysql_fetch_object($users)) { - $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); $to = $CONFIG->dataroot . user_file_matrix($user->guid); foreach (array('1_0', '1_1', '1_6') as $version) { diff --git a/engine/lib/upgrades/2010033101.php b/engine/lib/upgrades/2010033101.php index 5c8ee036b..4779295fd 100644 --- a/engine/lib/upgrades/2010033101.php +++ b/engine/lib/upgrades/2010033101.php @@ -1,6 +1,7 @@ <?php -/* - * Conditional upgrade for UTF8 as described in http://trac.elgg.org/ticket/1928 + +/** + * Conditional upgrade for UTF8 as described in https://github.com/elgg/elgg/issues/1928 */ // get_version() returns the code version. @@ -66,4 +67,4 @@ if ($dbversion < 2009100701) { } } } -}
\ No newline at end of file +} diff --git a/engine/lib/upgrades/2010040201.php b/engine/lib/upgrades/2010040201.php index 22eee15f8..789bf5dfc 100644 --- a/engine/lib/upgrades/2010040201.php +++ b/engine/lib/upgrades/2010040201.php @@ -1,4 +1,5 @@ <?php + /** * Pull admin metadata setting into users_entity table column */ @@ -37,4 +38,4 @@ $qs[] = "DELETE FROM {$CONFIG->dbprefix}metadata foreach ($qs as $q) { update_data($q); -}
\ No newline at end of file +} diff --git a/engine/lib/upgrades/2010050701.php b/engine/lib/upgrades/2010050701.php deleted file mode 100644 index 4a02a77c6..000000000 --- a/engine/lib/upgrades/2010050701.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -/** - * Removes the Walled Garden plugin in favor of new system settings - */ - -$access = elgg_set_ignore_access(TRUE); - -if (is_plugin_enabled('walledgarden')) { - disable_plugin('walledgarden'); - set_config('allow_registration', FALSE); - set_config('walled_garden', TRUE); -} else { - set_config('allow_registration', TRUE); - set_config('walled_garden', FALSE); -} - -elgg_set_ignore_access($access); diff --git a/engine/lib/upgrades/2010052601.php b/engine/lib/upgrades/2010052601.php index 5b477910f..a9cca6dc5 100644 --- a/engine/lib/upgrades/2010052601.php +++ b/engine/lib/upgrades/2010052601.php @@ -9,14 +9,14 @@ $params = array('type' => 'group', $groups = elgg_get_entities($params); if ($groups) { foreach ($groups as $group) { - $group->name = html_entity_decode($group->name, ENT_COMPAT, 'UTF-8'); - $group->description = html_entity_decode($group->description, ENT_COMPAT, 'UTF-8'); - $group->briefdescription = html_entity_decode($group->briefdescription, ENT_COMPAT, 'UTF-8'); - $group->website = html_entity_decode($group->website, ENT_COMPAT, 'UTF-8'); + $group->name = _elgg_html_decode($group->name); + $group->description = _elgg_html_decode($group->description); + $group->briefdescription = _elgg_html_decode($group->briefdescription); + $group->website = _elgg_html_decode($group->website); if ($group->interests) { $tags = $group->interests; - foreach ($tags as $index=>$tag) { - $tags[$index] = html_entity_decode($tag, ENT_COMPAT, 'UTF-8'); + foreach ($tags as $index => $tag) { + $tags[$index] = _elgg_html_decode($tag); } $group->interests = $tags; } diff --git a/engine/lib/upgrades/2010060101.php b/engine/lib/upgrades/2010060101.php index 7772c42eb..bb7f7c1a6 100644 --- a/engine/lib/upgrades/2010060101.php +++ b/engine/lib/upgrades/2010060101.php @@ -10,7 +10,7 @@ delete_data($query); if ($CONFIG->simplecache_enabled) { datalist_set('simplecache_enabled', 1); - elgg_view_regenerate_simplecache(); + elgg_regenerate_simplecache(); } else { datalist_set('simplecache_enabled', 0); } diff --git a/engine/lib/upgrades/2010061501.php b/engine/lib/upgrades/2010061501.php index d230236fc..744c28fd5 100644 --- a/engine/lib/upgrades/2010061501.php +++ b/engine/lib/upgrades/2010061501.php @@ -1,13 +1,12 @@ <?php /** - * utf8 conversion and file merging for usernames with multibyte chars + * utf8 database conversion and file merging for usernames with multibyte chars * */ // check that we need to do the utf8 conversion // C&P logic from 2010033101 -set_time_limit(0); $dbversion = (int) datalist_get('version'); if ($dbversion < 2009100701) { @@ -46,7 +45,7 @@ if ($dbversion < 2009100701) { } } - global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE; + global $ENTITY_CACHE; /** Upgrade file locations @@ -61,7 +60,9 @@ if ($dbversion < 2009100701) { $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity WHERE username != ''", $link); while ($user = mysql_fetch_object($users)) { - $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); + $to = $CONFIG->dataroot . user_file_matrix($user->guid); foreach (array('1_0', '1_1', '1_6') as $version) { @@ -71,4 +72,4 @@ if ($dbversion < 2009100701) { } } } -}
\ No newline at end of file +} diff --git a/engine/lib/upgrades/2010071001.php b/engine/lib/upgrades/2010071001.php index 1b5d379d8..5594493a8 100644 --- a/engine/lib/upgrades/2010071001.php +++ b/engine/lib/upgrades/2010071001.php @@ -30,11 +30,12 @@ function user_file_matrix_2010071001($guid) { $sizes = array('large', 'medium', 'small', 'tiny', 'master', 'topbar'); -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE, $CONFIG; +global $ENTITY_CACHE, $CONFIG; $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity WHERE username != ''"); while ($user = mysql_fetch_object($users)) { - $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); $user_directory = user_file_matrix_2010071001($user->guid); if (!$user_directory) { diff --git a/engine/lib/upgrades/2010071002.php b/engine/lib/upgrades/2010071002.php index 30bd6538c..52aa15ef5 100644 --- a/engine/lib/upgrades/2010071002.php +++ b/engine/lib/upgrades/2010071002.php @@ -4,12 +4,13 @@ */ // loop through all users checking collections and notifications -global $DB_QUERY_CACHE, $DB_PROFILE, $ENTITY_CACHE, $CONFIG; +global $ENTITY_CACHE, $CONFIG; global $NOTIFICATION_HANDLERS; $users = mysql_query("SELECT guid, username FROM {$CONFIG->dbprefix}users_entity WHERE username != ''"); while ($user = mysql_fetch_object($users)) { - $DB_QUERY_CACHE = $DB_PROFILE = $ENTITY_CACHE = array(); + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); $user = get_entity($user->guid); foreach ($NOTIFICATION_HANDLERS as $method => $foo) { diff --git a/engine/lib/upgrades/2010100500.php b/engine/lib/upgrades/2010100500.php deleted file mode 100644 index 1f9587196..000000000 --- a/engine/lib/upgrades/2010100500.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php -/** - * Upgrades the oAuth Library plugin name - */ - -if (is_plugin_enabled('oauth')) { - disable_plugin('oauth'); - enable_plugin('oauth_lib'); -} diff --git a/engine/lib/upgrades/2010111501.php b/engine/lib/upgrades/2010111501.php new file mode 100644 index 000000000..15e4a7d35 --- /dev/null +++ b/engine/lib/upgrades/2010111501.php @@ -0,0 +1,33 @@ +<?php +/** + * Set validation metadata on unvalidated users to false rather than + * not existing. This is needed because of the change in how validation is + * being handled. + */ + +// turn off system log because of all the metadata this can create +elgg_unregister_event_handler('all', 'all', 'system_log_listener'); +elgg_unregister_event_handler('log', 'systemlog', 'system_log_default_logger'); + +$ia = elgg_set_ignore_access(TRUE); +$hidden_entities = access_get_show_hidden_status(); +access_show_hidden_entities(TRUE); + +$validated_id = get_metastring_id('validated'); +$one_id = get_metastring_id(1); + +$query = "SELECT guid FROM {$CONFIG->dbprefix}entities e + WHERE e.type = 'user' AND e.enabled = 'no' AND + NOT EXISTS ( + SELECT 1 FROM {$CONFIG->dbprefix}metadata md + WHERE md.entity_guid = e.guid + AND md.name_id = $validated_id + AND md.value_id = $one_id)"; + +$user_guids = mysql_query($query); +while ($user_guid = mysql_fetch_object($user_guids)) { + create_metadata($user_guid->guid, 'validated', false, '', 0, ACCESS_PUBLIC, false); +} + +access_show_hidden_entities($hidden_entities); +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2010121601.php b/engine/lib/upgrades/2010121601.php new file mode 100644 index 000000000..ad7d26adb --- /dev/null +++ b/engine/lib/upgrades/2010121601.php @@ -0,0 +1,9 @@ +<?php +/** + * Create friends river view has been changed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river + SET view='river/relationship/friend/create', action_type='create' + WHERE view='friends/river/create' AND action_type='friend'"; +update_data($query); diff --git a/engine/lib/upgrades/2010121602.php b/engine/lib/upgrades/2010121602.php new file mode 100644 index 000000000..5b0996b5e --- /dev/null +++ b/engine/lib/upgrades/2010121602.php @@ -0,0 +1,10 @@ +<?php +/** + * Create comment river view has been changed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river + SET view='river/annotation/generic_comment/create' + WHERE view='annotation/annotate' AND action_type='comment'"; +update_data($query); + diff --git a/engine/lib/upgrades/2010121701.php b/engine/lib/upgrades/2010121701.php new file mode 100644 index 000000000..375654bac --- /dev/null +++ b/engine/lib/upgrades/2010121701.php @@ -0,0 +1,10 @@ +<?php +/** + * Create group forum topic river view has been changed + */ + +$query = "UPDATE {$CONFIG->dbprefix}river + SET view='river/object/groupforumtopic/create' + WHERE view='river/forum/topic/create' AND action_type='create'"; +update_data($query); + diff --git a/engine/lib/upgrades/2010123101.php b/engine/lib/upgrades/2010123101.php new file mode 100644 index 000000000..f4befd1a8 --- /dev/null +++ b/engine/lib/upgrades/2010123101.php @@ -0,0 +1,9 @@ +<?php +/** + * Set default access for older sites + */ + +$access = elgg_get_config('default_access'); +if ($access == false) { + elgg_save_config('default_access', ACCESS_LOGGED_IN); +} diff --git a/engine/lib/upgrades/2011010101.php b/engine/lib/upgrades/2011010101.php new file mode 100644 index 000000000..f4411ee20 --- /dev/null +++ b/engine/lib/upgrades/2011010101.php @@ -0,0 +1,98 @@ +<?php +/** + * Migrate plugins to the new system using ElggPlugin and private settings + */ + +$old_ia = elgg_set_ignore_access(true); + +$site = get_config('site'); +$old_plugin_order = unserialize($site->pluginorder); +$old_enabled_plugins = $site->enabled_plugins; + +$db_prefix = get_config('dbprefix'); +$plugin_subtype_id = get_subtype_id('object', 'plugin'); + +// easy one first: make sure the the site owns all plugin entities. +$q = "UPDATE {$db_prefix}entities e + SET owner_guid = $site->guid, container_guid = $site->guid + WHERE e.type = 'object' AND e.subtype = $plugin_subtype_id"; + +$r = update_data($q); + +// rewrite all plugin:setting:* to ELGG_PLUGIN_USER_SETTING_PREFIX . * +$q = "UPDATE {$db_prefix}private_settings + SET name = replace(name, 'plugin:settings:', '" . ELGG_PLUGIN_USER_SETTING_PREFIX . "') + WHERE name LIKE 'plugin:settings:%'"; + +$r = update_data($q); + +// grab current plugin GUIDs to add a temp priority +$q = "SELECT * FROM {$db_prefix}entities e + JOIN {$db_prefix}objects_entity oe ON e.guid = oe.guid + WHERE e.type = 'object' AND e.subtype = $plugin_subtype_id"; + +$plugins = get_data($q); + +foreach ($plugins as $plugin) { + $priority = elgg_namespace_plugin_private_setting('internal', 'priority'); + set_private_setting($plugin->guid, $priority, 0); +} + +// force regenerating plugin entities +elgg_generate_plugin_entities(); + +// set the priorities for all plugins +// this function rewrites it to a normal index so use the current one. +elgg_set_plugin_priorities($old_plugin_order); + +// add relationships for enabled plugins +if ($old_enabled_plugins) { + // they might only have one plugin enabled. + if (!is_array($old_enabled_plugins)) { + $old_enabled_plugins = array($old_enabled_plugins); + } + + // sometimes there were problems and you'd get 1000s of enabled plugins. + $old_enabled_plugins = array_unique($old_enabled_plugins); + + foreach ($old_enabled_plugins as $plugin_id) { + $plugin = elgg_get_plugin_from_id($plugin_id); + + if ($plugin) { + $plugin->activate(); + } + } +} + +// invalidate caches +elgg_invalidate_simplecache(); +elgg_reset_system_cache(); + +// clean up. +remove_metadata($site->guid, 'pluginorder'); +remove_metadata($site->guid, 'enabled_plugins'); + +elgg_set_ignore_access($old_id); + +/** + * @hack + * + * We stop the upgrade at this point because plugins weren't given the chance to + * load due to the new plugin code introduced with Elgg 1.8. Instead, we manually + * set the version and start the upgrade process again. + * + * The variables from upgrade_code() are available because this script was included + */ +if ($upgrade_version > $version) { + datalist_set('version', $upgrade_version); +} + +// add ourselves to the processed_upgrades. +$processed_upgrades[] = '2011010101.php'; + +$processed_upgrades = array_unique($processed_upgrades); +elgg_set_processed_upgrades($processed_upgrades); + +_elgg_upgrade_unlock(); + +forward('upgrade.php'); diff --git a/engine/lib/upgrades/2011021800-1.8_svn-goodbye_walled_garden-083121a656d06894.php b/engine/lib/upgrades/2011021800-1.8_svn-goodbye_walled_garden-083121a656d06894.php new file mode 100644 index 000000000..40b2c71d5 --- /dev/null +++ b/engine/lib/upgrades/2011021800-1.8_svn-goodbye_walled_garden-083121a656d06894.php @@ -0,0 +1,34 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011021800 + * goodbye_walled_garden + * + * Removes the Walled Garden plugin in favor of new system settings + */ + +global $CONFIG; + +$access = elgg_set_ignore_access(TRUE); + +if (elgg_is_active_plugin('walledgarden')) { + disable_plugin('walledgarden'); + set_config('allow_registration', FALSE); + set_config('walled_garden', TRUE); +} else { + set_config('allow_registration', TRUE); + set_config('walled_garden', FALSE); +} + +// this was for people who manually set the config option +$disable_registration = elgg_get_config('disable_registration'); +if ($disable_registration !== null) { + $allow_registration = !$disable_registration; + elgg_save_config('allow_registration', $allow_registration); + + $site = elgg_get_site_entity(); + $query = "DELETE FROM {$CONFIG->dbprefix}config + WHERE name = 'disable_registration' AND site_guid = $site->guid"; + delete_data($query); +} + +elgg_set_ignore_access($access); diff --git a/engine/lib/upgrades/2011022000-1.8_svn-custom_profile_fields-390ac967b0bb5665.php b/engine/lib/upgrades/2011022000-1.8_svn-custom_profile_fields-390ac967b0bb5665.php new file mode 100644 index 000000000..7561b84ba --- /dev/null +++ b/engine/lib/upgrades/2011022000-1.8_svn-custom_profile_fields-390ac967b0bb5665.php @@ -0,0 +1,59 @@ +<?php +/** + * Elgg 2011010401 upgrade 00 + * custom_profile_fields + * + * Migrate 1.7 style custom profile fields to 1.8 + */ + +$plugin = elgg_get_plugin_from_id('profile'); + +// plugin not installed +if (!$plugin) { + return true; +} + +$settings = $plugin->getAllSettings(); +// no fields to migrate +if (!$settings['user_defined_fields']) { + return true; +} + +$order = array(); +$remove_settings = array(); + +// make sure we have a name and type +foreach ($settings as $k => $v) { + if (!preg_match('/admin_defined_profile_([0-9]+)/i', $k, $matches)) { + continue; + } + + $i = $matches[1]; + $type_name = "admin_defined_profile_type_$i"; + $type = elgg_extract($type_name, $settings, null); + + if ($type) { + // field name + elgg_save_config($k, $v); + // field value + elgg_save_config($type_name, $type); + + $order[] = $i; + $remove_settings[] = $k; + $remove_settings[] = $type_name; + } +} + +if ($order) { + // these will always need to be in order, but there might be gaps + ksort($order); + + $order_str = implode(',', $order); + elgg_save_config('profile_custom_fields', $order_str); + + foreach ($remove_settings as $name) { + $plugin->unsetSetting($name); + } + + $plugin->unsetSetting('user_defined_fields'); +}
\ No newline at end of file diff --git a/engine/lib/upgrades/2011030700-1.8_svn-blog_status_metadata-4645225d7b440876.php b/engine/lib/upgrades/2011030700-1.8_svn-blog_status_metadata-4645225d7b440876.php new file mode 100644 index 000000000..fe2af9928 --- /dev/null +++ b/engine/lib/upgrades/2011030700-1.8_svn-blog_status_metadata-4645225d7b440876.php @@ -0,0 +1,24 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011030700 + * blog_status_metadata + * + * Add a "status" metadata entry to every blog entity because in 1.8 you can have status = draft or + * status = published + */ +$ia = elgg_set_ignore_access(true); +$options = array( + 'type' => 'object', + 'subtype' => 'blog', + 'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { + if (!$entity->status) { + // create metadata owned by the original owner + create_metadata($entity->getGUID(), 'status', 'published', '', $entity->owner_guid, + $entity->access_id); + } +} +elgg_set_ignore_access($ia);
\ No newline at end of file diff --git a/engine/lib/upgrades/2011031300-1.8_svn-twitter_api-12b832a5a7a3e1bd.php b/engine/lib/upgrades/2011031300-1.8_svn-twitter_api-12b832a5a7a3e1bd.php new file mode 100644 index 000000000..df60892a6 --- /dev/null +++ b/engine/lib/upgrades/2011031300-1.8_svn-twitter_api-12b832a5a7a3e1bd.php @@ -0,0 +1,54 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011031300 + * twitter_api + * + * Updates the database for twitterservice to twitter_api changes. + */ + + +$ia = elgg_set_ignore_access(true); + +// make sure we have updated plugins +elgg_generate_plugin_entities(); + +$show_hidden = access_get_show_hidden_status(); +access_show_hidden_entities(true); + +$db_prefix = elgg_get_config('dbprefix'); +$site_guid = elgg_get_site_entity()->getGUID(); +$old = elgg_get_plugin_from_id('twitterservice'); +$new = elgg_get_plugin_from_id('twitter_api'); +$has_settings = false; + +// if not loaded, don't bother. +if (!$old || !$new) { + return true; +} + +$settings = array('consumer_key', 'consumer_secret', 'sign_on', 'new_users'); + +foreach ($settings as $setting) { + $value = $old->getSetting($setting); + if ($value) { + $has_settings = true; + $new->setSetting($setting, $value); + } +} + +// update the user settings +$q = "UPDATE {$db_prefix}private_settings + SET name = replace(name, 'twitterservice', 'twitter_api') + WHERE name like '%twitterservice%'"; + +update_data($q); + +// if there were settings, emit a notice to re-enable twitter_api +if ($has_settings) { + elgg_add_admin_notice('twitter_api:disabled', elgg_echo('update:twitter_api:deactivated')); +} + +$old->delete(); + +access_show_hidden_entities($show_hidden); +elgg_set_ignore_access($ia);
\ No newline at end of file diff --git a/engine/lib/upgrades/2011031600-1.8_svn-datalist_grows_up-0b8aec5a55cc1e1c.php b/engine/lib/upgrades/2011031600-1.8_svn-datalist_grows_up-0b8aec5a55cc1e1c.php new file mode 100644 index 000000000..379244b36 --- /dev/null +++ b/engine/lib/upgrades/2011031600-1.8_svn-datalist_grows_up-0b8aec5a55cc1e1c.php @@ -0,0 +1,18 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011031600 + * datalist_grows_up + * + * Ups the varchar to 256 for the datalist and config table. + * + * Keeping it as a varchar because of the trailing whitespace trimming it apparently does: + * http://dev.mysql.com/doc/refman/5.0/en/char.html + */ + +$db_prefix = elgg_get_config('dbprefix'); + +$q = "ALTER TABLE {$db_prefix}datalists CHANGE name name VARCHAR(255)"; +update_data($q); + +$q = "ALTER TABLE {$db_prefix}config CHANGE name name VARCHAR(255)"; +update_data($q); diff --git a/engine/lib/upgrades/2011032000-1.8_svn-widgets_arent_plugins-61836261fa280a5c.php b/engine/lib/upgrades/2011032000-1.8_svn-widgets_arent_plugins-61836261fa280a5c.php new file mode 100644 index 000000000..a20970d79 --- /dev/null +++ b/engine/lib/upgrades/2011032000-1.8_svn-widgets_arent_plugins-61836261fa280a5c.php @@ -0,0 +1,10 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011031800 + * widgets_arent_plugins + * + * At some point in Elgg's history subtype widget was registered with class ElggPlugin. + * Fix that. + */ + +update_subtype('object', 'widget', 'ElggWidget'); diff --git a/engine/lib/upgrades/2011032200-1.8_svn-admins_like_widgets-7f19d2783c1680d3.php b/engine/lib/upgrades/2011032200-1.8_svn-admins_like_widgets-7f19d2783c1680d3.php new file mode 100644 index 000000000..592adb403 --- /dev/null +++ b/engine/lib/upgrades/2011032200-1.8_svn-admins_like_widgets-7f19d2783c1680d3.php @@ -0,0 +1,13 @@ +<?php +/** + * Elgg 1.8-svn upgrade 2011032200 + * admins_like_widgets + * + * Give current admins widgets for those pre-1.8 + */ + +$admins = elgg_get_admins(array('limit' => 0)); +foreach ($admins as $admin) { + // call the admin handler for the make_admin event + elgg_add_admin_widgets('make_admin', 'user', $admin); +} diff --git a/engine/lib/upgrades/2011052801.php b/engine/lib/upgrades/2011052801.php new file mode 100644 index 000000000..b5a8e1018 --- /dev/null +++ b/engine/lib/upgrades/2011052801.php @@ -0,0 +1,46 @@ +<?php +/** + * Make sure all users have the relationship member_of_site + */ +global $ENTITY_CACHE; +$db_prefix = get_config('dbprefix'); + +$limit = 100; + +$q = "SELECT e.* FROM {$db_prefix}entities e + WHERE e.type = 'user' AND e.guid NOT IN ( + SELECT guid_one FROM {$db_prefix}entity_relationships + WHERE guid_two = 1 AND relationship = 'member_of_site' + ) + LIMIT $limit"; + +$users = get_data($q); + +while ($users) { + $ENTITY_CACHE = array(); + _elgg_invalidate_query_cache(); + + // do manually to not trigger any events because these aren't new users. + foreach ($users as $user) { + $rel_q = "INSERT INTO {$db_prefix}entity_relationships VALUES ( + '', + '$user->guid', + 'member_of_site', + '$user->site_guid', + '$user->time_created' + )"; + + insert_data($rel_q); + } + + // every time we run this query we've just reduced the rows it returns by $limit + // so don't pass an offset. + $q = "SELECT e.* FROM {$db_prefix}entities e + WHERE e.type = 'user' AND e.guid NOT IN ( + SELECT guid_one FROM {$db_prefix}entity_relationships + WHERE guid_two = 1 AND relationship = 'member_of_site' + ) + LIMIT $limit"; + + $users = get_data($q); +}
\ No newline at end of file diff --git a/engine/lib/upgrades/2011061200-1.8b1-sites_need_a_site_guid-6d9dcbf46c0826cc.php b/engine/lib/upgrades/2011061200-1.8b1-sites_need_a_site_guid-6d9dcbf46c0826cc.php new file mode 100644 index 000000000..41ab29998 --- /dev/null +++ b/engine/lib/upgrades/2011061200-1.8b1-sites_need_a_site_guid-6d9dcbf46c0826cc.php @@ -0,0 +1,31 @@ +<?php +/** + * Elgg 1.8b1 upgrade 2011061200 + * sites_need_a_site_guid + * + * Sites did not have a site guid. This causes problems with getting + * metadata on site objects since we default to the current site. + */ + +global $CONFIG; + +$ia = elgg_set_ignore_access(true); +$access_status = access_get_show_hidden_status(); +access_show_hidden_entities(true); + +$options = array( + 'type' => 'site', + 'site_guid' => 0, + 'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { + if (!$entity->site_guid) { + update_data("UPDATE {$CONFIG->dbprefix}entities SET site_guid=$entity->guid + WHERE guid=$entity->guid"); + } +} + +access_show_hidden_entities($access_status); +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2011092500-1.8.0.1-forum_reply_river_view-5758ce8d86ac56ce.php b/engine/lib/upgrades/2011092500-1.8.0.1-forum_reply_river_view-5758ce8d86ac56ce.php new file mode 100644 index 000000000..3a9200b51 --- /dev/null +++ b/engine/lib/upgrades/2011092500-1.8.0.1-forum_reply_river_view-5758ce8d86ac56ce.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.0.1 upgrade 2011092500 + * forum_reply_river_view + * + * The forum reply river view is in a new location in Elgg 1.8 + */ + +$query = "UPDATE {$CONFIG->dbprefix}river SET view='river/annotation/group_topic_post/reply', + action_type='reply' + WHERE view='river/forum/create' AND action_type='create'"; +update_data($query); diff --git a/engine/lib/upgrades/2011123100-1.8.2-fix_friend_river-b17e7ff8345c2269.php b/engine/lib/upgrades/2011123100-1.8.2-fix_friend_river-b17e7ff8345c2269.php new file mode 100644 index 000000000..4dc43cd32 --- /dev/null +++ b/engine/lib/upgrades/2011123100-1.8.2-fix_friend_river-b17e7ff8345c2269.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.2 upgrade 2011123100 + * fix_friend_river + * + * Action type was incorrect due to previoud friends river upgrade + */ + +$query = "UPDATE {$CONFIG->dbprefix}river + SET action_type='friend' + WHERE view='river/relationship/friend/create' AND action_type='create'"; +update_data($query); diff --git a/engine/lib/upgrades/2011123101-1.8.2-fix_blog_status-b14c2a0e7b9e7d55.php b/engine/lib/upgrades/2011123101-1.8.2-fix_blog_status-b14c2a0e7b9e7d55.php new file mode 100644 index 000000000..e351c6ac9 --- /dev/null +++ b/engine/lib/upgrades/2011123101-1.8.2-fix_blog_status-b14c2a0e7b9e7d55.php @@ -0,0 +1,25 @@ +<?php +/** + * Elgg 1.8.2 upgrade 2011123101 + * fix_blog_status + * + * Most blog posts did not have their status properly set with 1.8 upgrade so we run + * the blog status upgrade again + */ + +$ia = elgg_set_ignore_access(true); +$options = array( + 'type' => 'object', + 'subtype' => 'blog', + 'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { + if (!$entity->status) { + // create metadata owned by the original owner + create_metadata($entity->getGUID(), 'status', 'published', '', $entity->owner_guid, + $entity->access_id); + } +} +elgg_set_ignore_access($ia);
\ No newline at end of file diff --git a/engine/lib/upgrades/2012012000-1.8.3-ip_in_syslog-87fe0f068cf62428.php b/engine/lib/upgrades/2012012000-1.8.3-ip_in_syslog-87fe0f068cf62428.php new file mode 100644 index 000000000..b9514e156 --- /dev/null +++ b/engine/lib/upgrades/2012012000-1.8.3-ip_in_syslog-87fe0f068cf62428.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012012000 + * ip_in_syslog + * + * Adds a field for an IP address in the system log table + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}system_log ADD ip_address VARCHAR(15) NOT NULL AFTER time_created"; + +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2012012100-1.8.3-system_cache-93100e7d55a24a11.php b/engine/lib/upgrades/2012012100-1.8.3-system_cache-93100e7d55a24a11.php new file mode 100644 index 000000000..3a9aae2a1 --- /dev/null +++ b/engine/lib/upgrades/2012012100-1.8.3-system_cache-93100e7d55a24a11.php @@ -0,0 +1,13 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012012100 + * system_cache + * + * Convert viewpath cache to system cache + */ + +$value = datalist_get('viewpath_cache_enabled'); +datalist_set('system_cache_enabled', $value); + +$query = "DELETE FROM {$CONFIG->dbprefix}datalists WHERE name='viewpath_cache_enabled'"; +delete_data($query); diff --git a/engine/lib/upgrades/2012041800-1.8.3-dont_filter_passwords-c0ca4a18b38ae2bc.php b/engine/lib/upgrades/2012041800-1.8.3-dont_filter_passwords-c0ca4a18b38ae2bc.php new file mode 100644 index 000000000..b82ffbebf --- /dev/null +++ b/engine/lib/upgrades/2012041800-1.8.3-dont_filter_passwords-c0ca4a18b38ae2bc.php @@ -0,0 +1,11 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012041800 + * dont_filter_passwords + * + * Add admin notice that password handling has changed and if + * users can't login to have them reset their passwords. + */ +elgg_add_admin_notice('dont_filter_passwords', 'Password handling has been updated to be more secure and flexible. ' + . 'This change may prevent a small number of users from logging in with their existing passwords. ' + . 'If a user is unable to log in, please advise him or her to reset their password, or reset it as an admin user.'); diff --git a/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php b/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php new file mode 100644 index 000000000..780038c32 --- /dev/null +++ b/engine/lib/upgrades/2012041801-1.8.3-multiple_user_tokens-852225f7fd89f6c5.php @@ -0,0 +1,13 @@ +<?php +/** + * Elgg 1.8.3 upgrade 2012041801 + * multiple_user_tokens + * + * Fixes https://github.com/elgg/elgg/issues/4291 + * Removes the unique index on users_apisessions for user_guid and site_guid + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}users_apisessions DROP INDEX user_guid, + ADD INDEX user_guid (user_guid, site_guid)"; +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php b/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php new file mode 100644 index 000000000..8eccf05e2 --- /dev/null +++ b/engine/lib/upgrades/2013030600-1.8.13-update_user_location-8999eb8bf1bdd9a3.php @@ -0,0 +1,24 @@ +<?php +/** + * Elgg 1.8.14 upgrade 2013030600 + * update_user_location + * + * Before Elgg 1.8, a location like "London, England" would be stored as an array. + * This script turns that back into a string. + */ + +$ia = elgg_set_ignore_access(true); +$options = array( + 'type' => 'user', + 'limit' => 0, +); +$batch = new ElggBatch('elgg_get_entities', $options); + +foreach ($batch as $entity) { + _elgg_invalidate_query_cache(); + + if (is_array($entity->location)) { + $entity->location = implode(', ', $entity->location); + } +} +elgg_set_ignore_access($ia); diff --git a/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php b/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php new file mode 100644 index 000000000..ee99bdbc8 --- /dev/null +++ b/engine/lib/upgrades/2013051700-1.8.15-add_missing_group_index-52a63a3a3ffaced2.php @@ -0,0 +1,28 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013051700 + * add_missing_group_index + * + * Some Elgg sites are missing the groups_entity full text index on name and + * description. This checks if it exists and adds it if it does not. + */ + +$db_prefix = elgg_get_config('dbprefix'); + +$full_text_index_exists = false; +$results = get_data("SHOW INDEX FROM {$db_prefix}groups_entity"); +if ($results) { + foreach ($results as $result) { + if ($result->Index_type === 'FULLTEXT') { + $full_text_index_exists = true; + } + } +} + +if ($full_text_index_exists == false) { + $query = "ALTER TABLE {$db_prefix}groups_entity + ADD FULLTEXT name_2 (name, description)"; + if (!update_data($query)) { + elgg_log("Failed to add full text index to groups_entity table", 'ERROR'); + } +} diff --git a/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php b/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php new file mode 100644 index 000000000..d333a6cd2 --- /dev/null +++ b/engine/lib/upgrades/2013052900-1.8.15-ipv6_in_syslog-f5c2cc0196e9e731.php @@ -0,0 +1,12 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013052900 + * ipv6_in_syslog + * + * Upgrade the ip column in system_log to be able to store ipv6 addresses + */ + +$db_prefix = elgg_get_config('dbprefix'); +$q = "ALTER TABLE {$db_prefix}system_log MODIFY COLUMN ip_address varchar(46) NOT NULL"; + +update_data($q);
\ No newline at end of file diff --git a/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php new file mode 100644 index 000000000..538d74dd6 --- /dev/null +++ b/engine/lib/upgrades/2013060900-1.8.15-site_secret-404fc165cf9e0ac9.php @@ -0,0 +1,16 @@ +<?php +/** + * Elgg 1.8.15 upgrade 2013060900 + * site_secret + * + * Description + */ + +$strength = _elgg_get_site_secret_strength(); + +if ($strength !== 'strong') { + // a new key is needed immediately + register_translations(elgg_get_root_path() . 'languages/'); + + elgg_add_admin_notice('weak_site_key', elgg_echo("upgrade:site_secret_warning:$strength")); +} diff --git a/engine/lib/upgrades/create_upgrade.php b/engine/lib/upgrades/create_upgrade.php new file mode 100644 index 000000000..b34f31b7e --- /dev/null +++ b/engine/lib/upgrades/create_upgrade.php @@ -0,0 +1,152 @@ +<?php +/** + * Creates an upgrade file for Elgg. + * + * Run this from the command line: + * php create_upgrade.php upgrade_name + */ + +error_reporting(E_NOTICE); + +// only allow from the command line. +if (php_sapi_name() != 'cli') { + die('Upgrades can only be created from the command line.'); +} + +if (count($argv) < 2) { + elgg_create_upgrade_show_usage('No upgrade name.'); +} + +$name = $argv[1]; + +if (strlen($name) > 24) { + elgg_create_upgrade_show_usage('Upgrade names cannot be longer than 24 characters.'); +} + +require_once '../../../version.php'; +require_once '../elgglib.php'; +$upgrade_path = dirname(__FILE__); + +$upgrade_name = strtolower($name); +$upgrade_name = str_replace(array(' ', '-'), '_', $upgrade_name); +$upgrade_release = str_replace(array(' ', '-'), '_', $release); +$time = time(); +$upgrade_rnd = substr(md5($time), 0, 16); +$upgrade_date = date('Ymd', $time); + +// determine the inc count +$upgrade_inc = 0; +$files = elgg_get_file_list($upgrade_path); +sort($files); + +foreach ($files as $filename) { + $filename = basename($filename); + $date = (int)substr($filename, 0, 8); + $inc = (int)substr($filename, 8, 2); + + if ($upgrade_date == $date) { + if ($inc >= $upgrade_inc) { + $upgrade_inc = $inc + 1; + } + } +} + +// zero-pad +// if there are more than 10 upgrades in a day, someone needs talking to. +if ($upgrade_inc < 10) { + $upgrade_inc = "0$upgrade_inc"; +} + +$upgrade_version = $upgrade_date . $upgrade_inc; + +// make filename +if (substr($release, 0, 3) == '1.7') { + // 1.7 upgrades are YYYYMMDDXX + $upgrade_name = $upgrade_version . '.php'; +} else { + // 1.8+ upgrades are YYYYMMDDXX-release-friendly_name-rnd + $upgrade_name = $upgrade_version . "-$upgrade_release-$name-$upgrade_rnd.php"; +} + +$upgrade_file = $upgrade_path . '/' . $upgrade_name; + +if (is_file($upgrade_file)) { + elgg_create_upgrade_show_usage("Upgrade file $upgrade_file already exists. This script has failed you."); +} + +$upgrade_code = <<<___UPGRADE +<?php +/** + * Elgg $release upgrade $upgrade_version + * $name + * + * Description + */ + +// upgrade code here. + +___UPGRADE; + +$h = fopen($upgrade_file, 'wb'); + +if (!$h) { + die("Could not open file $upgrade_file"); +} + +if (!fwrite($h, $upgrade_code)) { + die("Could not write to $upgrade_file"); +} else { + elgg_set_version_dot_php_version($upgrade_version); + echo <<<___MSG + +Created upgrade file and updated version.php. + +Upgrade file: $upgrade_name +Version: $upgrade_version + +___MSG; +} + +fclose($h); + + +function elgg_set_version_dot_php_version($version) { + $file = '../../../version.php'; + $h = fopen($file, 'r+b'); + + if (!$h) { + return false; + } + + $out = ''; + + while (($line = fgets($h)) !== false) { + $find = "/\\\$version[ ]?=[ ]?[0-9]{10};/"; + $replace = "\$version = $version;"; + $out .= preg_replace($find, $replace, $line); + } + + rewind($h); + + fwrite($h, $out); + fclose($h); + return true; +} + +/** + * Shows the usage for the create_upgrade script and dies(). + * + * @param string $msg Optional message to display + * @return void + */ +function elgg_create_upgrade_show_usage($msg = '') { + $text = <<<___MSG +$msg + +Example: + php create_upgrade.php my_upgrade + +___MSG; + + die($text); +} diff --git a/engine/lib/user_settings.php b/engine/lib/user_settings.php new file mode 100644 index 000000000..0e36dc46d --- /dev/null +++ b/engine/lib/user_settings.php @@ -0,0 +1,360 @@ +<?php +/** + * Elgg user settings functions. + * Functions for adding and manipulating options on the user settings panel. + * + * @package Elgg.Core + * @subpackage Settings.User + */ + +/** + * Saves user settings. + * + * @todo this assumes settings are coming in on a GET/POST request + * + * @note This is a handler for the 'usersettings:save', 'user' plugin hook + * + * @return void + * @access private + */ +function users_settings_save() { + elgg_set_user_language(); + elgg_set_user_password(); + elgg_set_user_default_access(); + elgg_set_user_name(); + elgg_set_user_email(); +} + +/** + * Set a user's password + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_password() { + $current_password = get_input('current_password', null, false); + $password = get_input('password', null, false); + $password2 = get_input('password2', null, false); + $user_guid = get_input('guid'); + + if (!$user_guid) { + $user = elgg_get_logged_in_user_entity(); + } else { + $user = get_entity($user_guid); + } + + if ($user && $password) { + // let admin user change anyone's password without knowing it except his own. + if (!elgg_is_admin_logged_in() || elgg_is_admin_logged_in() && $user->guid == elgg_get_logged_in_user_guid()) { + $credentials = array( + 'username' => $user->username, + 'password' => $current_password + ); + + try { + pam_auth_userpass($credentials); + } catch (LoginException $e) { + register_error(elgg_echo('LoginException:ChangePasswordFailure')); + return false; + } + } + + try { + $result = validate_password($password); + } catch (RegistrationException $e) { + register_error($e->getMessage()); + return false; + } + + if ($result) { + if ($password == $password2) { + $user->salt = generate_random_cleartext_password(); // Reset the salt + $user->password = generate_user_password($user, $password); + if ($user->save()) { + system_message(elgg_echo('user:password:success')); + return true; + } else { + register_error(elgg_echo('user:password:fail')); + } + } else { + register_error(elgg_echo('user:password:fail:notsame')); + } + } else { + register_error(elgg_echo('user:password:fail:tooshort')); + } + } else { + // no change + return null; + } + + return false; +} + +/** + * Set a user's display name + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_name() { + $name = strip_tags(get_input('name')); + $user_id = get_input('guid'); + + if (!$user_id) { + $user = elgg_get_logged_in_user_entity(); + } else { + $user = get_entity($user_id); + } + + if (elgg_strlen($name) > 50) { + register_error(elgg_echo('user:name:fail')); + return false; + } + + if (($user) && ($user->canEdit()) && ($name)) { + if ($name != $user->name) { + $user->name = $name; + if ($user->save()) { + system_message(elgg_echo('user:name:success')); + return true; + } else { + register_error(elgg_echo('user:name:fail')); + } + } else { + // no change + return null; + } + } else { + register_error(elgg_echo('user:name:fail')); + } + return false; +} + +/** + * Set a user's language + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_language() { + $language = get_input('language'); + $user_id = get_input('guid'); + + if (!$user_id) { + $user = elgg_get_logged_in_user_entity(); + } else { + $user = get_entity($user_id); + } + + if (($user) && ($language)) { + if (strcmp($language, $user->language) != 0) { + $user->language = $language; + if ($user->save()) { + system_message(elgg_echo('user:language:success')); + return true; + } else { + register_error(elgg_echo('user:language:fail')); + } + } else { + // no change + return null; + } + } else { + register_error(elgg_echo('user:language:fail')); + } + return false; +} + +/** + * Set a user's email address + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_email() { + $email = get_input('email'); + $user_id = get_input('guid'); + + if (!$user_id) { + $user = elgg_get_logged_in_user_entity(); + } else { + $user = get_entity($user_id); + } + + if (!is_email_address($email)) { + register_error(elgg_echo('email:save:fail')); + return false; + } + + if ($user) { + if (strcmp($email, $user->email) != 0) { + if (!get_user_by_email($email)) { + if ($user->email != $email) { + + $user->email = $email; + if ($user->save()) { + system_message(elgg_echo('email:save:success')); + return true; + } else { + register_error(elgg_echo('email:save:fail')); + } + } + } else { + register_error(elgg_echo('registration:dupeemail')); + } + } else { + // no change + return null; + } + } else { + register_error(elgg_echo('email:save:fail')); + } + return false; +} + +/** + * Set a user's default access level + * + * @return bool + * @since 1.8.0 + * @access private + */ +function elgg_set_user_default_access() { + + if (!elgg_get_config('allow_user_default_access')) { + return false; + } + + $default_access = get_input('default_access'); + $user_id = get_input('guid'); + + if (!$user_id) { + $user = elgg_get_logged_in_user_entity(); + } else { + $user = get_entity($user_id); + } + + if ($user) { + $current_default_access = $user->getPrivateSetting('elgg_default_access'); + if ($default_access !== $current_default_access) { + if ($user->setPrivateSetting('elgg_default_access', $default_access)) { + system_message(elgg_echo('user:default_access:success')); + return true; + } else { + register_error(elgg_echo('user:default_access:fail')); + } + } else { + // no change + return null; + } + } else { + register_error(elgg_echo('user:default_access:fail')); + } + + return false; +} + +/** + * Set up the menu for user settings + * + * @return void + * @access private + */ +function usersettings_pagesetup() { + $user = elgg_get_page_owner_entity(); + + if ($user && elgg_get_context() == "settings") { + $params = array( + 'name' => '1_account', + 'text' => elgg_echo('usersettings:user:opt:linktext'), + 'href' => "settings/user/{$user->username}", + ); + elgg_register_menu_item('page', $params); + $params = array( + 'name' => '1_plugins', + 'text' => elgg_echo('usersettings:plugins:opt:linktext'), + 'href' => "settings/plugins/{$user->username}", + ); + elgg_register_menu_item('page', $params); + $params = array( + 'name' => '1_statistics', + 'text' => elgg_echo('usersettings:statistics:opt:linktext'), + 'href' => "settings/statistics/{$user->username}", + ); + elgg_register_menu_item('page', $params); + } +} + +/** + * Page handler for user settings + * + * @param array $page Pages array + * + * @return bool + * @access private + */ +function usersettings_page_handler($page) { + global $CONFIG; + + if (!isset($page[0])) { + $page[0] = 'user'; + } + + if (isset($page[1])) { + $user = get_user_by_username($page[1]); + elgg_set_page_owner_guid($user->guid); + } else { + $user = elgg_get_logged_in_user_entity(); + elgg_set_page_owner_guid($user->guid); + } + + elgg_push_breadcrumb(elgg_echo('settings'), "settings/user/$user->username"); + + switch ($page[0]) { + case 'statistics': + elgg_push_breadcrumb(elgg_echo('usersettings:statistics:opt:linktext')); + $path = $CONFIG->path . "pages/settings/statistics.php"; + break; + case 'plugins': + elgg_push_breadcrumb(elgg_echo('usersettings:plugins:opt:linktext')); + $path = $CONFIG->path . "pages/settings/tools.php"; + break; + case 'user': + $path = $CONFIG->path . "pages/settings/account.php"; + break; + } + + if (isset($path)) { + require $path; + return true; + } + return false; +} + +/** + * Initialize the user settings library + * + * @return void + * @access private + */ +function usersettings_init() { + elgg_register_page_handler('settings', 'usersettings_page_handler'); + + elgg_register_plugin_hook_handler('usersettings:save', 'user', 'users_settings_save'); + + elgg_register_action("usersettings/save"); + + // extend the account settings form + elgg_extend_view('forms/account/settings', 'core/settings/account/name', 100); + elgg_extend_view('forms/account/settings', 'core/settings/account/password', 100); + elgg_extend_view('forms/account/settings', 'core/settings/account/email', 100); + elgg_extend_view('forms/account/settings', 'core/settings/account/language', 100); + elgg_extend_view('forms/account/settings', 'core/settings/account/default_access', 100); +} + +elgg_register_event_handler('init', 'system', 'usersettings_init'); +elgg_register_event_handler('pagesetup', 'system', 'usersettings_pagesetup'); diff --git a/engine/lib/users.php b/engine/lib/users.php index 0075c9baa..a8fb9121c 100644 --- a/engine/lib/users.php +++ b/engine/lib/users.php @@ -8,9 +8,11 @@ */ /// Map a username to a cached GUID +global $USERNAME_TO_GUID_MAP_CACHE; $USERNAME_TO_GUID_MAP_CACHE = array(); /// Map a user code to a cached GUID +global $CODE_TO_GUID_MAP_CACHE; $CODE_TO_GUID_MAP_CACHE = array(); /** @@ -19,6 +21,7 @@ $CODE_TO_GUID_MAP_CACHE = array(); * @param int $guid The ElggUser guid * * @return mixed + * @access private */ function get_user_entity_as_row($guid) { global $CONFIG; @@ -28,7 +31,7 @@ function get_user_entity_as_row($guid) { } /** - * Create or update the extras table for a given user. + * Create or update the entities table for a given user. * Call create_entity first. * * @param int $guid The user's GUID @@ -41,6 +44,7 @@ function get_user_entity_as_row($guid) { * @param string $code A code * * @return bool + * @access private */ function create_user_entity($guid, $name, $username, $password, $salt, $email, $language, $code) { global $CONFIG; @@ -57,26 +61,25 @@ function create_user_entity($guid, $name, $username, $password, $salt, $email, $ $row = get_entity_as_row($guid); if ($row) { // Exists and you have access to it - $query = "SELECT guid from {$CONFIG->dbprefix}users_entity where guid = {$guid}"; if ($exists = get_data_row($query)) { $query = "UPDATE {$CONFIG->dbprefix}users_entity - set name='$name', username='$username', password='$password', salt='$salt', - email='$email', language='$language', code='$code', last_action = " - . time() . " where guid = {$guid}"; + SET name='$name', username='$username', password='$password', salt='$salt', + email='$email', language='$language', code='$code' + WHERE guid = $guid"; $result = update_data($query); if ($result != false) { // Update succeeded, continue $entity = get_entity($guid); - if (trigger_elgg_event('update', $entity->type, $entity)) { + if (elgg_trigger_event('update', $entity->type, $entity)) { return $guid; } else { $entity->delete(); } } } else { - // Update failed, attempt an insert. + // Exists query failed, attempt an insert. $query = "INSERT into {$CONFIG->dbprefix}users_entity (guid, name, username, password, salt, email, language, code) values ($guid, '$name', '$username', '$password', '$salt', '$email', '$language', '$code')"; @@ -84,10 +87,10 @@ function create_user_entity($guid, $name, $username, $password, $salt, $email, $ $result = insert_data($query); if ($result !== false) { $entity = get_entity($guid); - if (trigger_elgg_event('create', $entity->type, $entity)) { + if (elgg_trigger_event('create', $entity->type, $entity)) { return $guid; } else { - $entity->delete(); //delete_entity($guid); + $entity->delete(); } } } @@ -107,7 +110,7 @@ function disable_user_entities($owner_guid) { global $CONFIG; $owner_guid = (int) $owner_guid; if ($entity = get_entity($owner_guid)) { - if (trigger_elgg_event('disable', $entity->type, $entity)) { + if (elgg_trigger_event('disable', $entity->type, $entity)) { if ($entity->canEdit()) { $query = "UPDATE {$CONFIG->dbprefix}entities set enabled='no' where owner_guid={$owner_guid} @@ -134,12 +137,11 @@ function ban_user($user_guid, $reason = "") { global $CONFIG; $user_guid = (int)$user_guid; - $reason = sanitise_string($reason); $user = get_entity($user_guid); if (($user) && ($user->canEdit()) && ($user instanceof ElggUser)) { - if (trigger_elgg_event('ban', 'user', $user)) { + if (elgg_trigger_event('ban', 'user', $user)) { // Add reason if ($reason) { create_metadata($user_guid, 'ban_reason', $reason, '', 0, ACCESS_PUBLIC); @@ -185,7 +187,7 @@ function unban_user($user_guid) { $user = get_entity($user_guid); if (($user) && ($user->canEdit()) && ($user instanceof ElggUser)) { - if (trigger_elgg_event('unban', 'user', $user)) { + if (elgg_trigger_event('unban', 'user', $user)) { create_metadata($user_guid, 'ban_reason', '', '', 0, ACCESS_PUBLIC); // invalidate memcache for this user @@ -222,7 +224,7 @@ function make_user_admin($user_guid) { $user = get_entity((int)$user_guid); if (($user) && ($user instanceof ElggUser) && ($user->canEdit())) { - if (trigger_elgg_event('make_admin', 'user', $user)) { + if (elgg_trigger_event('make_admin', 'user', $user)) { // invalidate memcache for this user static $newentity_cache; @@ -235,7 +237,7 @@ function make_user_admin($user_guid) { } $r = update_data("UPDATE {$CONFIG->dbprefix}users_entity set admin='yes' where guid=$user_guid"); - invalidate_cache_for_entity($user_guid); + _elgg_invalidate_cache_for_entity($user_guid); return $r; } @@ -258,7 +260,7 @@ function remove_user_admin($user_guid) { $user = get_entity((int)$user_guid); if (($user) && ($user instanceof ElggUser) && ($user->canEdit())) { - if (trigger_elgg_event('remove_admin', 'user', $user)) { + if (elgg_trigger_event('remove_admin', 'user', $user)) { // invalidate memcache for this user static $newentity_cache; @@ -271,7 +273,7 @@ function remove_user_admin($user_guid) { } $r = update_data("UPDATE {$CONFIG->dbprefix}users_entity set admin='no' where guid=$user_guid"); - invalidate_cache_for_entity($user_guid); + _elgg_invalidate_cache_for_entity($user_guid); return $r; } @@ -282,30 +284,13 @@ function remove_user_admin($user_guid) { } /** - * THIS FUNCTION IS DEPRECATED. - * - * Delete a user's extra data. - * - * @todo remove - * - * @param int $guid User GUID - * - * @return 1 - */ -function delete_user_entity($guid) { - system_message(sprintf(elgg_echo('deprecatedfunction'), 'delete_user_entity')); - - return 1; // Always return that we have deleted one row in order to not break existing code. -} - -/** * Get the sites this user is part of * * @param int $user_guid The user's GUID * @param int $limit Number of results to return * @param int $offset Any indexing offset * - * @return false|array On success, an array of ElggSites + * @return ElggSite[]|false On success, an array of ElggSites */ function get_user_sites($user_guid, $limit = 10, $offset = 0) { $user_guid = (int)$user_guid; @@ -313,13 +298,14 @@ function get_user_sites($user_guid, $limit = 10, $offset = 0) { $offset = (int)$offset; return elgg_get_entities_from_relationship(array( + 'site_guids' => ELGG_ENTITIES_ANY_VALUE, 'relationship' => 'member_of_site', 'relationship_guid' => $user_guid, 'inverse_relationship' => FALSE, - 'types' => 'site', + 'type' => 'site', 'limit' => $limit, - 'offset' => $offset) - ); + 'offset' => $offset, + )); } /** @@ -357,8 +343,6 @@ function user_add_friend($user_guid, $friend_guid) { * @return bool Depending on success */ function user_remove_friend($user_guid, $friend_guid) { - global $CONFIG; - $user_guid = (int) $user_guid; $friend_guid = (int) $friend_guid; @@ -382,7 +366,7 @@ function user_remove_friend($user_guid, $friend_guid) { * @return bool */ function user_is_friend($user_guid, $friend_guid) { - return check_entity_relationship($user_guid, "friend", $friend_guid); + return check_entity_relationship($user_guid, "friend", $friend_guid) !== false; } /** @@ -393,7 +377,7 @@ function user_is_friend($user_guid, $friend_guid) { * @param int $limit Number of results to return (default 10) * @param int $offset Indexing offset, if any * - * @return false|array Either an array of ElggUsers or false, depending on success + * @return ElggUser[]|false Either an array of ElggUsers or false, depending on success */ function get_user_friends($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $offset = 0) { @@ -401,8 +385,8 @@ $offset = 0) { return elgg_get_entities_from_relationship(array( 'relationship' => 'friend', 'relationship_guid' => $user_guid, - 'types' => 'user', - 'subtypes' => $subtype, + 'type' => 'user', + 'subtype' => $subtype, 'limit' => $limit, 'offset' => $offset )); @@ -416,7 +400,7 @@ $offset = 0) { * @param int $limit Number of results to return (default 10) * @param int $offset Indexing offset, if any * - * @return false|array Either an array of ElggUsers or false, depending on success + * @return ElggUser[]|false Either an array of ElggUsers or false, depending on success */ function get_user_friends_of($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $offset = 0) { @@ -425,92 +409,11 @@ $offset = 0) { 'relationship' => 'friend', 'relationship_guid' => $user_guid, 'inverse_relationship' => TRUE, - 'types' => 'user', - 'subtypes' => $subtype, - 'limit' => $limit, - 'offset' => $offset - )); -} - -/** - * Obtains a list of objects owned by a user - * - * @param int $user_guid The GUID of the owning user - * @param string $subtype Optionally, the subtype of objects - * @param int $limit The number of results to return (default 10) - * @param int $offset Indexing offset, if any - * @param int $timelower The earliest time the entity can have been created. Default: all - * @param int $timeupper The latest time the entity can have been created. Default: all - * - * @return false|array An array of ElggObjects or false, depending on success - */ -function get_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, -$offset = 0, $timelower = 0, $timeupper = 0) { - - $ntt = elgg_get_entities(array( - 'type' => 'object', + 'type' => 'user', 'subtype' => $subtype, - 'owner_guid' => $user_guid, 'limit' => $limit, - 'offset' => $offset, - 'container_guid' => $user_guid, - 'created_time_lower' => $timelower, - 'created_time_upper' => $timeupper - )); - return $ntt; -} - -/** - * Counts the objects (optionally of a particular subtype) owned by a user - * - * @param int $user_guid The GUID of the owning user - * @param string $subtype Optionally, the subtype of objects - * @param int $timelower The earliest time the entity can have been created. Default: all - * @param int $timeupper The latest time the entity can have been created. Default: all - * - * @return int The number of objects the user owns (of this subtype) - */ -function count_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $timelower = 0, -$timeupper = 0) { - - $total = elgg_get_entities(array( - 'type' => 'object', - 'subtype' => $subtype, - 'owner_guid' => $user_guid, - 'count' => TRUE, - 'container_guid' => $user_guid, - 'created_time_lower' => $timelower, - 'created_time_upper' => $timeupper + 'offset' => $offset )); - return $total; -} - -/** - * Displays a list of user objects of a particular subtype, with navigation. - * - * @see elgg_view_entity_list - * - * @param int $user_guid The GUID of the user - * @param string $subtype The object subtype - * @param int $limit The number of entities to display on a page - * @param bool $fullview Whether or not to display the full view (default: true) - * @param bool $viewtypetoggle Whether or not to allow gallery view (default: true) - * @param bool $pagination Whether to display pagination (default: true) - * @param int $timelower The earliest time the entity can have been created. Default: all - * @param int $timeupper The latest time the entity can have been created. Default: all - * - * @return string The list in a form suitable to display - */ -function list_user_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, -$fullview = true, $viewtypetoggle = true, $pagination = true, $timelower = 0, $timeupper = 0) { - - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = (int) count_user_objects($user_guid, $subtype, $timelower, $timeupper); - $entities = get_user_objects($user_guid, $subtype, $limit, $offset, $timelower, $timeupper); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, $viewtypetoggle, - $pagination); } /** @@ -523,7 +426,7 @@ $fullview = true, $viewtypetoggle = true, $pagination = true, $timelower = 0, $t * @param int $timelower The earliest time the entity can have been created. Default: all * @param int $timeupper The latest time the entity can have been created. Default: all * - * @return false|array An array of ElggObjects or false, depending on success + * @return ElggObject[]|false An array of ElggObjects or false, depending on success */ function get_user_friends_objects($user_guid, $subtype = ELGG_ENTITIES_ANY_VALUE, $limit = 10, $offset = 0, $timelower = 0, $timeupper = 0) { @@ -586,43 +489,32 @@ $timelower = 0, $timeupper = 0) { * @param int $user_guid The GUID of the user * @param string $subtype The object subtype * @param int $limit The number of entities to display on a page - * @param bool $fullview Whether or not to display the full view (default: true) - * @param bool $viewtypetoggle Whether or not to allow you to flip to gallery mode (default: true) + * @param bool $full_view Whether or not to display the full view (default: true) + * @param bool $listtypetoggle Whether or not to allow you to flip to gallery mode (default: true) * @param bool $pagination Whether to display pagination (default: true) * @param int $timelower The earliest time the entity can have been created. Default: all * @param int $timeupper The latest time the entity can have been created. Default: all * - * @return string The list in a form suitable to display + * @return string */ -function list_user_friends_objects($user_guid, $subtype = "", $limit = 10, $fullview = true, -$viewtypetoggle = true, $pagination = true, $timelower = 0, $timeupper = 0) { +function list_user_friends_objects($user_guid, $subtype = "", $limit = 10, $full_view = true, +$listtypetoggle = true, $pagination = true, $timelower = 0, $timeupper = 0) { - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = (int) count_user_friends_objects($user_guid, $subtype, $timelower, $timeupper); + $offset = (int)get_input('offset'); + $limit = (int)$limit; + $count = (int)count_user_friends_objects($user_guid, $subtype, $timelower, $timeupper); $entities = get_user_friends_objects($user_guid, $subtype, $limit, $offset, $timelower, $timeupper); - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, - $viewtypetoggle, $pagination); -} - -/** - * Get user objects by an array of metadata - * - * @param int $user_guid The GUID of the owning user - * @param string $subtype Optionally, the subtype of objects - * @param array $metadata An array of metadata - * @param int $limit The number of results to return (default 10) - * @param int $offset Indexing offset, if any - * - * @return false|array An array of ElggObjects or false, depending on success - */ -function get_user_objects_by_metadata($user_guid, $subtype = "", $metadata = array(), -$limit = 0, $offset = 0) { - return get_entities_from_metadata_multi($metadata, "object", $subtype, $user_guid, - $limit, $offset); + return elgg_view_entity_list($entities, array( + 'count' => $count, + 'offset' => $offset, + 'limit' => $limit, + 'full_view' => $full_view, + 'list_type_toggle' => $listtypetoggle, + 'pagination' => $pagination, + )); } /** @@ -661,26 +553,32 @@ function get_user($guid) { function get_user_by_username($username) { global $CONFIG, $USERNAME_TO_GUID_MAP_CACHE; + // Fixes #6052. Username is frequently sniffed from the path info, which, + // unlike $_GET, is not URL decoded. If the username was not URL encoded, + // this is harmless. + $username = rawurldecode($username); + $username = sanitise_string($username); $access = get_access_sql_suffix('e'); // Caching if ((isset($USERNAME_TO_GUID_MAP_CACHE[$username])) - && (retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]))) { - return retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]); + && (_elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]))) { + return _elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]); } $query = "SELECT e.* from {$CONFIG->dbprefix}users_entity u join {$CONFIG->dbprefix}entities e on e.guid=u.guid where u.username='$username' and $access "; - $row = get_data_row($query); - if ($row) { - $USERNAME_TO_GUID_MAP_CACHE[$username] = $row->guid; - return new ElggUser($row); + $entity = get_data_row($query, 'entity_row_to_elggstar'); + if ($entity) { + $USERNAME_TO_GUID_MAP_CACHE[$username] = $entity->guid; + } else { + $entity = false; } - return false; + return $entity; } /** @@ -699,30 +597,29 @@ function get_user_by_code($code) { // Caching if ((isset($CODE_TO_GUID_MAP_CACHE[$code])) - && (retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]))) { + && (_elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]))) { - return retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]); + return _elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]); } $query = "SELECT e.* from {$CONFIG->dbprefix}users_entity u join {$CONFIG->dbprefix}entities e on e.guid=u.guid where u.code='$code' and $access"; - $row = get_data_row($query); - if ($row) { - $CODE_TO_GUID_MAP_CACHE[$code] = $row->guid; - return new ElggUser($row); + $entity = get_data_row($query, 'entity_row_to_elggstar'); + if ($entity) { + $CODE_TO_GUID_MAP_CACHE[$code] = $entity->guid; } - return false; + return $entity; } /** - * Get an array of users from their + * Get an array of users from an email address * * @param string $email Email address. * - * @return Array of users + * @return array */ function get_user_by_email($email) { global $CONFIG; @@ -739,103 +636,38 @@ function get_user_by_email($email) { } /** - * Searches for a user based on a complete or partial name or username. - * - * @param string $criteria The partial or full name or username. - * @param int $limit Limit of the search. - * @param int $offset Offset. - * @param string $order_by The order. - * @param boolean $count Whether to return the count of results or just the results. - * - * @return mixed - * @deprecated 1.7 - */ -function search_for_user($criteria, $limit = 10, $offset = 0, $order_by = "", $count = false) { - elgg_deprecated_notice('search_for_user() was deprecated by new search.', 1.7); - global $CONFIG; - - $criteria = sanitise_string($criteria); - $limit = (int)$limit; - $offset = (int)$offset; - $order_by = sanitise_string($order_by); - - $access = get_access_sql_suffix("e"); - - if ($order_by == "") { - $order_by = "e.time_created desc"; - } - - if ($count) { - $query = "SELECT count(e.guid) as total "; - } else { - $query = "SELECT e.* "; - } - $query .= "from {$CONFIG->dbprefix}entities e - join {$CONFIG->dbprefix}users_entity u on e.guid=u.guid where "; - - $query .= "(u.name like \"%{$criteria}%\" or u.username like \"%{$criteria}%\")"; - $query .= " and $access"; - - if (!$count) { - $query .= " order by $order_by limit $offset, $limit"; - return get_data($query, "entity_row_to_elggstar"); - } else { - if ($count = get_data_row($query)) { - return $count->total; - } - } - return false; -} - -/** - * Displays a list of user objects that have been searched for. - * - * @see elgg_view_entity_list - * - * @param string $tag Search criteria - * @param int $limit The number of entities to display on a page - * - * @return string The list in a form suitable to display - * - * @deprecated 1.7 - */ -function list_user_search($tag, $limit = 10) { - elgg_deprecated_notice('list_user_search() deprecated by new search', 1.7); - $offset = (int) get_input('offset'); - $limit = (int) $limit; - $count = (int) search_for_user($tag, 10, 0, '', true); - $entities = search_for_user($tag, $limit, $offset); - - return elgg_view_entity_list($entities, $count, $offset, $limit, $fullview, false); -} - -/** * A function that returns a maximum of $limit users who have done something within the last - * $seconds seconds. + * $seconds seconds or the total count of active users. * - * @param int $seconds Number of seconds (default 600 = 10min) - * @param int $limit Limit, default 10. - * @param int $offset Offset, defualt 0. + * @param int $seconds Number of seconds (default 600 = 10min) + * @param int $limit Limit, default 10. + * @param int $offset Offset, default 0. + * @param bool $count Count, default false. * * @return mixed */ -function find_active_users($seconds = 600, $limit = 10, $offset = 0) { - global $CONFIG; - +function find_active_users($seconds = 600, $limit = 10, $offset = 0, $count = false) { $seconds = (int)$seconds; $limit = (int)$limit; $offset = (int)$offset; + $params = array('seconds' => $seconds, 'limit' => $limit, 'offset' => $offset, 'count' => $count); + $data = elgg_trigger_plugin_hook('find_active_users', 'system', $params, NULL); + if (!$data) { + global $CONFIG; - $time = time() - $seconds; + $time = time() - $seconds; - $access = get_access_sql_suffix("e"); - - $query = "SELECT distinct e.* from {$CONFIG->dbprefix}entities e - join {$CONFIG->dbprefix}users_entity u on e.guid = u.guid - where u.last_action >= {$time} and $access - order by u.last_action desc limit {$offset}, {$limit}"; - - return get_data($query, "entity_row_to_elggstar"); + $data = elgg_get_entities(array( + 'type' => 'user', + 'limit' => $limit, + 'offset' => $offset, + 'count' => $count, + 'joins' => array("join {$CONFIG->dbprefix}users_entity u on e.guid = u.guid"), + 'wheres' => array("u.last_action >= {$time}"), + 'order_by' => "u.last_action desc" + )); + } + return $data; } /** @@ -846,25 +678,22 @@ function find_active_users($seconds = 600, $limit = 10, $offset = 0) { * @return bool */ function send_new_password_request($user_guid) { - global $CONFIG; - $user_guid = (int)$user_guid; $user = get_entity($user_guid); - if ($user) { + if ($user instanceof ElggUser) { // generate code $code = generate_random_cleartext_password(); - //create_metadata($user_guid, 'conf_code', $code, '', 0, ACCESS_PRIVATE); - set_private_setting($user_guid, 'passwd_conf_code', $code); + $user->setPrivateSetting('passwd_conf_code', $code); // generate link - $link = $CONFIG->site->url . "pg/resetpassword?u=$user_guid&c=$code"; + $link = elgg_get_site_url() . "resetpassword?u=$user_guid&c=$code"; // generate email - $email = sprintf(elgg_echo('email:resetreq:body'), $user->name, $_SERVER['REMOTE_ADDR'], $link); + $email = elgg_echo('email:resetreq:body', array($user->name, $_SERVER['REMOTE_ADDR'], $link)); - return notify_user($user->guid, $CONFIG->site->guid, - elgg_echo('email:resetreq:subject'), $email, NULL, 'email'); + return notify_user($user->guid, elgg_get_site_entity()->guid, + elgg_echo('email:resetreq:subject'), $email, array(), 'email'); } return false; @@ -881,21 +710,18 @@ function send_new_password_request($user_guid) { * @return bool */ function force_user_password_reset($user_guid, $password) { - global $CONFIG; - - if (call_gatekeeper('execute_new_password_request', __FILE__)) { - $user = get_entity($user_guid); + $user = get_entity($user_guid); + if ($user instanceof ElggUser) { + $ia = elgg_set_ignore_access(); - if ($user) { - $salt = generate_random_cleartext_password(); // Reset the salt - $user->salt = $salt; + $user->salt = generate_random_cleartext_password(); + $hash = generate_user_password($user, $password); + $user->password = $hash; + $result = (bool)$user->save(); - $hash = generate_user_password($user, $password); + elgg_set_ignore_access($ia); - $query = "UPDATE {$CONFIG->dbprefix}users_entity - set password='$hash', salt='$salt' where guid=$user_guid"; - return update_data($query); - } + return $result; } return false; @@ -915,18 +741,22 @@ function execute_new_password_request($user_guid, $conf_code) { $user_guid = (int)$user_guid; $user = get_entity($user_guid); - $saved_code = get_private_setting($user_guid, 'passwd_conf_code'); - - if ($user && $saved_code && $saved_code == $conf_code) { - $password = generate_random_cleartext_password(); + if ($user instanceof ElggUser) { + $saved_code = $user->getPrivateSetting('passwd_conf_code'); - if (force_user_password_reset($user_guid, $password)) { - remove_private_setting($user_guid, 'passwd_conf_code'); + if ($saved_code && $saved_code == $conf_code) { + $password = generate_random_cleartext_password(); - $email = sprintf(elgg_echo('email:resetpassword:body'), $user->name, $password); + if (force_user_password_reset($user_guid, $password)) { + remove_private_setting($user_guid, 'passwd_conf_code'); + // clean the logins failures + reset_login_failure_count($user_guid); + + $email = elgg_echo('email:resetpassword:body', array($user->name, $password)); - return notify_user($user->guid, $CONFIG->site->guid, - elgg_echo('email:resetpassword:subject'), $email, NULL, 'email'); + return notify_user($user->guid, $CONFIG->site->guid, + elgg_echo('email:resetpassword:subject'), $email, array(), 'email'); + } } } @@ -934,54 +764,6 @@ function execute_new_password_request($user_guid, $conf_code) { } /** - * Handles pages for password reset requests. - * - * @param array $page Pages array - * - * @return void - */ -function elgg_user_resetpassword_page_handler($page) { - global $CONFIG; - - $user_guid = get_input('u'); - $code = get_input('c'); - - $user = get_entity($user_guid); - - // don't check code here to avoid automated attacks - if (!$user instanceof ElggUser) { - register_error(elgg_echo('user:passwordreset:unknown_user')); - forward(); - } - - $form_body = elgg_echo('user:resetpassword:reset_password_confirm') . "<br />"; - - $form_body .= elgg_view('input/hidden', array( - 'internalname' => 'u', - 'value' => $user_guid - )); - - $form_body .= elgg_view('input/hidden', array( - 'internalname' => 'c', - 'value' => $code - )); - - $form_body .= elgg_view('input/submit', array( - 'value' => elgg_echo('resetpassword') - )); - - $form .= elgg_view('input/form', array( - 'body' => $form_body, - 'action' => 'action/user/passwordreset' - )); - - $title = elgg_echo('resetpassword'); - $content = elgg_view_title(elgg_echo('resetpassword')) . $form; - - echo elgg_view_page($title, elgg_view_layout('one_column', $content)); -} - -/** * Simple function that will generate a random clear text password * suitable for feeding into generate_user_password(). * @@ -1024,7 +806,14 @@ function validate_username($username) { } if (strlen($username) < $CONFIG->minusername) { - throw new RegistrationException(elgg_echo('registration:usernametooshort')); + $msg = elgg_echo('registration:usernametooshort', array($CONFIG->minusername)); + throw new RegistrationException($msg); + } + + // username in the database has a limit of 128 characters + if (strlen($username) > 128) { + $msg = elgg_echo('registration:usernametoolong', array(128)); + throw new RegistrationException($msg); } // Blacklist for bad characters (partially nicked from mediawiki) @@ -1040,6 +829,7 @@ function validate_username($username) { if ( preg_match($blacklist, $username) ) { + // @todo error message needs work throw new RegistrationException(elgg_echo('registration:invalidchars')); } @@ -1049,13 +839,14 @@ function validate_username($username) { for ($n = 0; $n < strlen($blacklist2); $n++) { if (strpos($username, $blacklist2[$n]) !== false) { - $msg = sprintf(elgg_echo('registration:invalidchars'), $blacklist2[$n], $blacklist2); + $msg = elgg_echo('registration:invalidchars', array($blacklist2[$n], $blacklist2)); + $msg = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8'); throw new RegistrationException($msg); } } $result = true; - return trigger_plugin_hook('registeruser:validate:username', 'all', + return elgg_trigger_plugin_hook('registeruser:validate:username', 'all', array('username' => $username), $result); } @@ -1070,12 +861,17 @@ function validate_username($username) { function validate_password($password) { global $CONFIG; + if (!isset($CONFIG->min_password_length)) { + $CONFIG->min_password_length = 6; + } + if (strlen($password) < $CONFIG->min_password_length) { - throw new RegistrationException(elgg_echo('registration:passwordtooshort')); + $msg = elgg_echo('registration:passwordtooshort', array($CONFIG->min_password_length)); + throw new RegistrationException($msg); } $result = true; - return trigger_plugin_hook('registeruser:validate:password', 'all', + return elgg_trigger_plugin_hook('registeruser:validate:password', 'all', array('password' => $password), $result); } @@ -1094,7 +890,7 @@ function validate_email_address($address) { // Got here, so lets try a hook (defaulting to ok) $result = true; - return trigger_plugin_hook('registeruser:validate:email', 'all', + return elgg_trigger_plugin_hook('registeruser:validate:email', 'all', array('email' => $address), $result); } @@ -1111,13 +907,11 @@ function validate_email_address($address) { * @param string $invitecode An invite code from a friend * * @return int|false The new user's GUID; false on failure + * @throws RegistrationException */ function register_user($username, $password, $name, $email, $allow_multiple_emails = false, $friend_guid = 0, $invitecode = '') { - // Load the configuration - global $CONFIG; - // no need to trim password. $username = trim($username); $name = trim(strip_tags($name)); @@ -1167,6 +961,7 @@ $allow_multiple_emails = false, $friend_guid = 0, $invitecode = '') { $user->password = generate_user_password($user, $password); $user->owner_guid = 0; // Users aren't owned by anyone, even if they are admin created. $user->container_guid = 0; // Users aren't contained by anyone, even if they are admin created. + $user->language = get_current_language(); $user->save(); // If $friend_guid has been set, make mutual friends @@ -1177,28 +972,12 @@ $allow_multiple_emails = false, $friend_guid = 0, $invitecode = '') { $friend_user->addFriend($user->guid); // @todo Should this be in addFriend? - add_to_river('friends/river/create', 'friend', $user->getGUID(), $friend_guid); - add_to_river('friends/river/create', 'friend', $friend_guid, $user->getGUID()); + add_to_river('river/relationship/friend/create', 'friend', $user->getGUID(), $friend_guid); + add_to_river('river/relationship/friend/create', 'friend', $friend_guid, $user->getGUID()); } } } - // Check to see if we've registered the first admin yet. - // If not, this is the first admin user! - $have_admin = datalist_get('admin_registered'); - - if (!$have_admin) { - // makeAdmin() calls ElggUser::canEdit(). - // right now no one is logged in and so canEdit() returns false. - // instead of making an override for this one instance that is called on every - // canEdit() call, just override the access system to set the first admin user. - // @todo remove this when Cash merges in the new installer - $ia = elgg_set_ignore_access(TRUE); - $user->makeAdmin(); - datalist_set('admin_registered', 1); - elgg_set_ignore_access($ia); - } - // Turn on email notifications by default set_user_notification_setting($user->getGUID(), 'email', true); @@ -1218,54 +997,94 @@ function generate_invite_code($username) { } /** - * Adds collection submenu items + * Set the validation status for a user. * - * @return void + * @param int $user_guid The user's GUID + * @param bool $status Validated (true) or unvalidated (false) + * @param string $method Optional method to say how a user was validated + * @return bool + * @since 1.8.0 */ -function collections_submenu_items() { - global $CONFIG; - $user = get_loggedin_user(); +function elgg_set_user_validation_status($user_guid, $status, $method = '') { + $result1 = create_metadata($user_guid, 'validated', $status, '', 0, ACCESS_PUBLIC, false); + $result2 = create_metadata($user_guid, 'validated_method', $method, '', 0, ACCESS_PUBLIC, false); + if ($result1 && $result2) { + return true; + } else { + return false; + } +} - add_submenu_item(elgg_echo('friends:collections'), - $CONFIG->wwwroot . "pg/collections/" . $user->username); +/** + * Gets the validation status of a user. + * + * @param int $user_guid The user's GUID + * @return bool|null Null means status was not set for this user. + * @since 1.8.0 + */ +function elgg_get_user_validation_status($user_guid) { + $md = elgg_get_metadata(array( + 'guid' => $user_guid, + 'metadata_name' => 'validated' + )); + if ($md == false) { + return null; + } - add_submenu_item(elgg_echo('friends:collections:add'), $CONFIG->wwwroot . "pg/collections/add"); + if ($md[0]->value) { + return true; + } + + return false; } /** - * Page handler for friends - * - * @param array $page_elements Page elements + * Adds collection submenu items * * @return void + * @access private */ -function friends_page_handler($page_elements) { - if (isset($page_elements[0]) && $user = get_user_by_username($page_elements[0])) { - set_page_owner($user->getGUID()); - } - if (get_loggedin_userid() == elgg_get_page_owner_guid()) { - // disabled for now as we no longer use friends collections (replaced by shared access) - // collections_submenu_items(); - } - require_once(dirname(dirname(dirname(__FILE__))) . "/pages/friends/index.php"); +function collections_submenu_items() { + + $user = elgg_get_logged_in_user_entity(); + + elgg_register_menu_item('page', array( + 'name' => 'friends:view:collections', + 'text' => elgg_echo('friends:collections'), + 'href' => "collections/$user->username", + )); } /** - * Page handler for friends of + * Page handler for friends-related pages * - * @param array $page_elements Page elements + * @param array $segments URL segments + * @param string $handler The first segment in URL used for routing * - * @return void + * @return bool + * @access private */ -function friends_of_page_handler($page_elements) { - if (isset($page_elements[0]) && $user = get_user_by_username($page_elements[0])) { - set_page_owner($user->getGUID()); +function friends_page_handler($segments, $handler) { + elgg_set_context('friends'); + + if (isset($segments[0]) && $user = get_user_by_username($segments[0])) { + elgg_set_page_owner_guid($user->getGUID()); } - if (get_loggedin_userid() == elgg_get_page_owner_guid()) { - // disabled for now as we no longer use friends collections (replaced by shared access) - // collections_submenu_items(); + if (elgg_get_logged_in_user_guid() == elgg_get_page_owner_guid()) { + collections_submenu_items(); } - require_once(dirname(dirname(dirname(__FILE__))) . "/pages/friends/of.php"); + + switch ($handler) { + case 'friends': + require_once(dirname(dirname(dirname(__FILE__))) . "/pages/friends/index.php"); + break; + case 'friendsof': + require_once(dirname(dirname(dirname(__FILE__))) . "/pages/friends/of.php"); + break; + default: + return false; + } + return true; } /** @@ -1273,66 +1092,63 @@ function friends_of_page_handler($page_elements) { * * @param array $page_elements Page elements * - * @return void + * @return bool + * @access private */ function collections_page_handler($page_elements) { + gatekeeper(); + elgg_set_context('friends'); + $base = elgg_get_config('path'); if (isset($page_elements[0])) { if ($page_elements[0] == "add") { - set_page_owner(get_loggedin_userid()); + elgg_set_page_owner_guid(elgg_get_logged_in_user_guid()); collections_submenu_items(); - require_once(dirname(dirname(dirname(__FILE__))) . "/pages/friends/add.php"); + require_once "{$base}pages/friends/collections/add.php"; + return true; } else { - if ($user = get_user_by_username($page_elements[0])) { - set_page_owner($user->getGUID()); - if (get_loggedin_userid() == elgg_get_page_owner_guid()) { + $user = get_user_by_username($page_elements[0]); + if ($user) { + elgg_set_page_owner_guid($user->getGUID()); + if (elgg_get_logged_in_user_guid() == elgg_get_page_owner_guid()) { collections_submenu_items(); } - require_once(dirname(dirname(dirname(__FILE__))) . "/pages/friends/collections.php"); + require_once "{$base}pages/friends/collections/view.php"; + return true; } } } + return false; } /** - * Page handler for dashboard - * - * @param array $page_elements Page elements - * - * @return void - */ -function dashboard_page_handler($page_elements) { - require_once(dirname(dirname(dirname(__FILE__))) . "/pages/dashboard/index.php"); -} - - -/** - * Page handler for registration - * - * @param array $page_elements Page elements - * - * @return void - */ -function registration_page_handler($page_elements) { - require_once(dirname(dirname(dirname(__FILE__))) . "/pages/account/register.php"); -} - -/** - * Display a login box. + * Page handler for account related pages * - * This is a fallback for non-JS users who click on the - * dropdown login link. + * @param array $page_elements Page elements + * @param string $handler The handler string * - * @return void - * @todo finish + * @return bool + * @access private */ -function elgg_user_login_page_handler() { - $content = elgg_view_layout('one_column', elgg_view('account/forms/login')); - $content = ' - <div id="elgg_content" class="clearfloat"> - ' . elgg_view('account/forms/login') . ' - </div> - '; - echo elgg_view_page('test', $content); +function elgg_user_account_page_handler($page_elements, $handler) { + + $base_dir = elgg_get_root_path() . 'pages/account'; + switch ($handler) { + case 'login': + require_once("$base_dir/login.php"); + break; + case 'forgotpassword': + require_once("$base_dir/forgotten_password.php"); + break; + case 'resetpassword': + require_once("$base_dir/reset_password.php"); + break; + case 'register': + require_once("$base_dir/register.php"); + break; + default: + return false; + } + return true; } /** @@ -1359,7 +1175,7 @@ function set_last_action($user_guid) { * * @param int $user_guid The user GUID * - * @return boid + * @return void */ function set_last_login($user_guid) { $user_guid = (int) $user_guid; @@ -1379,168 +1195,409 @@ function set_last_login($user_guid) { * @param string $object_type user * @param ElggUser $object User object * - * @return bool + * @return void + * @access private */ function user_create_hook_add_site_relationship($event, $object_type, $object) { - global $CONFIG; - - add_entity_relationship($object->getGUID(), 'member_of_site', $CONFIG->site->getGUID()); + add_entity_relationship($object->getGUID(), 'member_of_site', elgg_get_site_entity()->guid); } /** - * Sets up user-related menu items + * Serves the user's avatar * - * @return void + * @param string $hook + * @param string $entity_type + * @param string $returnvalue + * @param array $params + * @return string + * @access private */ -function users_pagesetup() { - // Load config - global $CONFIG; +function user_avatar_hook($hook, $entity_type, $returnvalue, $params) { + $user = $params['entity']; + $size = $params['size']; - //add submenu options - if (elgg_get_context() == "friends" || elgg_get_context() == "friendsof") { - // || elgg_get_context() == "collections") { - disabled as we no longer use collections + if (isset($user->icontime)) { + return "avatar/view/$user->username/$size/$user->icontime"; + } else { + return "_graphics/icons/user/default{$size}.gif"; + } +} - add_submenu_item(elgg_echo('friends'), $CONFIG->wwwroot . "pg/friends/" - . elgg_get_page_owner()->username); +/** + * Setup the default user hover menu + * @access private + */ +function elgg_user_hover_menu($hook, $type, $return, $params) { + $user = $params['entity']; + /* @var ElggUser $user */ + + if (elgg_is_logged_in()) { + if (elgg_get_logged_in_user_guid() != $user->guid) { + if ($user->isFriend()) { + $url = "action/friends/remove?friend={$user->guid}"; + $text = elgg_echo('friend:remove'); + $name = 'remove_friend'; + } else { + $url = "action/friends/add?friend={$user->guid}"; + $text = elgg_echo('friend:add'); + $name = 'add_friend'; + } + $url = elgg_add_action_tokens_to_url($url); + $item = new ElggMenuItem($name, $text, $url); + $item->setSection('action'); + $return[] = $item; + } else { + $url = "profile/$user->username/edit"; + $item = new ElggMenuItem('profile:edit', elgg_echo('profile:edit'), $url); + $item->setSection('action'); + $return[] = $item; + + $url = "avatar/edit/$user->username"; + $item = new ElggMenuItem('avatar:edit', elgg_echo('avatar:edit'), $url); + $item->setSection('action'); + $return[] = $item; + } + } - add_submenu_item(elgg_echo('friends:of'), $CONFIG->wwwroot . "pg/friendsof/" - . elgg_get_page_owner()->username); + // prevent admins from banning or deleting themselves + if (elgg_get_logged_in_user_guid() == $user->guid) { + return $return; + } - if (is_plugin_enabled('members')) { - add_submenu_item(elgg_echo('members:browse'), $CONFIG->wwwroot . "mod/members/index.php"); + if (elgg_is_admin_logged_in()) { + $actions = array(); + if (!$user->isBanned()) { + $actions[] = 'ban'; + } else { + $actions[] = 'unban'; } + $actions[] = 'delete'; + $actions[] = 'resetpassword'; + if (!$user->isAdmin()) { + $actions[] = 'makeadmin'; + } else { + $actions[] = 'removeadmin'; + } + + foreach ($actions as $action) { + $url = "action/admin/user/$action?guid={$user->guid}"; + $url = elgg_add_action_tokens_to_url($url); + $item = new ElggMenuItem($action, elgg_echo($action), $url); + $item->setSection('admin'); + $item->setLinkClass('elgg-requires-confirmation'); + + $return[] = $item; + } + + $url = "profile/$user->username/edit"; + $item = new ElggMenuItem('profile:edit', elgg_echo('profile:edit'), $url); + $item->setSection('admin'); + $return[] = $item; + + $url = "settings/user/$user->username"; + $item = new ElggMenuItem('settings:edit', elgg_echo('settings:edit'), $url); + $item->setSection('admin'); + $return[] = $item; } + + return $return; } /** - * Users initialisation function, which establishes the page handler + * Setup the menu shown with an entity * - * @return void + * @param string $hook + * @param string $type + * @param array $return + * @param array $params + * @return array + * + * @access private */ -function users_init() { - // Load config - global $CONFIG; +function elgg_users_setup_entity_menu($hook, $type, $return, $params) { + if (elgg_in_context('widgets')) { + return $return; + } - // add Friends to tools menu - if profile mod is running - // now added to toolbar - /* - if ( isloggedin() && is_plugin_enabled('profile') ) { - $user = get_loggedin_user(); - add_menu(elgg_echo('friends'), $CONFIG->wwwroot . - "pg/friends/" . $user->username, array(), 'core:friends'); + $entity = $params['entity']; + if (!elgg_instanceof($entity, 'user')) { + return $return; + } + /* @var ElggUser $entity */ + + if ($entity->isBanned()) { + $banned = elgg_echo('banned'); + $options = array( + 'name' => 'banned', + 'text' => "<span>$banned</span>", + 'href' => false, + 'priority' => 0, + ); + $return = array(ElggMenuItem::factory($options)); + } else { + $return = array(); + if (isset($entity->location)) { + $location = htmlspecialchars($entity->location, ENT_QUOTES, 'UTF-8', false); + $options = array( + 'name' => 'location', + 'text' => "<span>$location</span>", + 'href' => false, + 'priority' => 150, + ); + $return[] = ElggMenuItem::factory($options); + } } - */ - register_page_handler('friends', 'friends_page_handler'); - register_page_handler('friendsof', 'friends_of_page_handler'); - register_page_handler('dashboard', 'dashboard_page_handler'); - register_page_handler('register', 'registration_page_handler'); - register_page_handler('resetpassword', 'elgg_user_resetpassword_page_handler'); - register_page_handler('login', 'elgg_user_login_page_handler'); + return $return; +} - register_action("register", true); - register_action("useradd", true); - register_action("friends/add"); - register_action("friends/remove"); - //register_action('friends/addcollection'); - //register_action('friends/deletecollection'); - //register_action('friends/editcollection'); - //register_action("user/spotlight"); +/** + * This function loads a set of default fields into the profile, then triggers a hook letting other plugins to edit + * add and delete fields. + * + * Note: This is a secondary system:init call and is run at a super low priority to guarantee that it is called after all + * other plugins have initialised. + * @access private + */ +function elgg_profile_fields_setup() { + global $CONFIG; - register_action("usersettings/save"); + $profile_defaults = array ( + 'description' => 'longtext', + 'briefdescription' => 'text', + 'location' => 'location', + 'interests' => 'tags', + 'skills' => 'tags', + 'contactemail' => 'email', + 'phone' => 'text', + 'mobile' => 'text', + 'website' => 'url', + 'twitter' => 'text' + ); - register_action("user/passwordreset", TRUE); - register_action("user/requestnewpassword", TRUE); + $loaded_defaults = array(); + if ($fieldlist = elgg_get_config('profile_custom_fields')) { + if (!empty($fieldlist)) { + $fieldlistarray = explode(',', $fieldlist); + foreach ($fieldlistarray as $listitem) { + if ($translation = elgg_get_config("admin_defined_profile_{$listitem}")) { + $type = elgg_get_config("admin_defined_profile_type_{$listitem}"); + $loaded_defaults["admin_defined_profile_{$listitem}"] = $type; + add_translation(get_current_language(), array("profile:admin_defined_profile_{$listitem}" => $translation)); + } + } + } + } - // User name change - extend_elgg_settings_page('user/settings/name', 'usersettings/user', 1); - //register_action("user/name"); + if (count($loaded_defaults)) { + $CONFIG->profile_using_custom = true; + $profile_defaults = $loaded_defaults; + } - // User password change - extend_elgg_settings_page('user/settings/password', 'usersettings/user', 1); - //register_action("user/password"); + $CONFIG->profile_fields = elgg_trigger_plugin_hook('profile:fields', 'profile', NULL, $profile_defaults); - // Add email settings - extend_elgg_settings_page('user/settings/email', 'usersettings/user', 1); - //register_action("email/save"); + // register any tag metadata names + foreach ($CONFIG->profile_fields as $name => $type) { + if ($type == 'tags' || $type == 'location' || $type == 'tag') { + elgg_register_tag_metadata_name($name); + // register a tag name translation + add_translation(get_current_language(), array("tag_names:$name" => elgg_echo("profile:$name"))); + } + } +} - // Add language settings - extend_elgg_settings_page('user/settings/language', 'usersettings/user', 1); +/** + * Avatar page handler + * + * /avatar/edit/<username> + * /avatar/view/<username>/<size>/<icontime> + * + * @param array $page + * @return bool + * @access private + */ +function elgg_avatar_page_handler($page) { + global $CONFIG; - // Add default access settings - extend_elgg_settings_page('user/settings/default_access', 'usersettings/user', 1); + $user = get_user_by_username($page[1]); + if ($user) { + elgg_set_page_owner_guid($user->getGUID()); + } - //register_action("user/language"); + if ($page[0] == 'edit') { + require_once("{$CONFIG->path}pages/avatar/edit.php"); + return true; + } else { + set_input('size', $page[2]); + require_once("{$CONFIG->path}pages/avatar/view.php"); + return true; + } + return false; +} - // Register the user type - register_entity_type('user', ''); +/** + * Profile page handler + * + * @param array $page + * @return bool + * @access private + */ +function elgg_profile_page_handler($page) { + global $CONFIG; - register_plugin_hook('usersettings:save', 'user', 'users_settings_save'); + $user = get_user_by_username($page[0]); + elgg_set_page_owner_guid($user->guid); - register_elgg_event_handler('create', 'user', 'user_create_hook_add_site_relationship'); + if ($page[1] == 'edit') { + require_once("{$CONFIG->path}pages/profile/edit.php"); + return true; + } + return false; } /** - * Returns a formatted list of users suitable for injecting into search. - * - * @deprecated 1.7 - * - * @param string $hook Hook name - * @param string $user User? - * @param mixed $returnvalue Previous hook's return value - * @param mixed $tag Tag to search against + * Sets up user-related menu items * * @return void + * @access private */ -function search_list_users_by_name($hook, $user, $returnvalue, $tag) { - elgg_deprecated_notice('search_list_users_by_name() was deprecated by new search', 1.7); - // Change this to set the number of users that display on the search page - $threshold = 4; +function users_pagesetup() { - $object = get_input('object'); + $owner = elgg_get_page_owner_entity(); + $viewer = elgg_get_logged_in_user_entity(); + + if ($owner) { + $params = array( + 'name' => 'friends', + 'text' => elgg_echo('friends'), + 'href' => 'friends/' . $owner->username, + 'contexts' => array('friends') + ); + elgg_register_menu_item('page', $params); + + $params = array( + 'name' => 'friends:of', + 'text' => elgg_echo('friends:of'), + 'href' => 'friendsof/' . $owner->username, + 'contexts' => array('friends') + ); + elgg_register_menu_item('page', $params); + + elgg_register_menu_item('page', array( + 'name' => 'edit_avatar', + 'href' => "avatar/edit/{$owner->username}", + 'text' => elgg_echo('avatar:edit'), + 'contexts' => array('profile_edit'), + )); - if (!get_input('offset') && (empty($object) || $object == 'user')) { - if ($users = search_for_user($tag, $threshold)) { - $countusers = search_for_user($tag, 0, 0, "", true); + elgg_register_menu_item('page', array( + 'name' => 'edit_profile', + 'href' => "profile/{$owner->username}/edit", + 'text' => elgg_echo('profile:edit'), + 'contexts' => array('profile_edit'), + )); + } - $return = elgg_view('user/search/startblurb', array('count' => $countusers, 'tag' => $tag)); - foreach ($users as $user) { - $return .= elgg_view_entity($user); - } + // topbar + if ($viewer) { + elgg_register_menu_item('topbar', array( + 'name' => 'profile', + 'href' => $viewer->getURL(), + 'text' => elgg_view('output/img', array( + 'src' => $viewer->getIconURL('topbar'), + 'alt' => $viewer->name, + 'title' => elgg_echo('profile'), + 'class' => 'elgg-border-plain elgg-transition', + )), + 'priority' => 100, + 'link_class' => 'elgg-topbar-avatar', + )); - $vars = array('count' => $countusers, 'threshold' => $threshold, 'tag' => $tag); - $return .= elgg_view('user/search/finishblurb', $vars); - return $return; + elgg_register_menu_item('topbar', array( + 'name' => 'friends', + 'href' => "friends/{$viewer->username}", + 'text' => elgg_view_icon('users'), + 'title' => elgg_echo('friends'), + 'priority' => 300, + )); - } + elgg_register_menu_item('topbar', array( + 'name' => 'usersettings', + 'href' => "settings/user/{$viewer->username}", + 'text' => elgg_view_icon('settings') . elgg_echo('settings'), + 'priority' => 500, + 'section' => 'alt', + )); + + elgg_register_menu_item('topbar', array( + 'name' => 'logout', + 'href' => "action/logout", + 'text' => elgg_echo('logout'), + 'is_action' => TRUE, + 'priority' => 1000, + 'section' => 'alt', + )); } } /** - * Saves user settings by directly including actions. - * - * @todo this is dirty. + * Users initialisation function, which establishes the page handler * * @return void + * @access private */ -function users_settings_save() { - global $CONFIG; - include($CONFIG->path . "actions/user/name.php"); - include($CONFIG->path . "actions/user/password.php"); - include($CONFIG->path . "actions/email/save.php"); - include($CONFIG->path . "actions/user/language.php"); - include($CONFIG->path . "actions/user/default_access.php"); +function users_init() { + + elgg_register_page_handler('friends', 'friends_page_handler'); + elgg_register_page_handler('friendsof', 'friends_page_handler'); + elgg_register_page_handler('register', 'elgg_user_account_page_handler'); + elgg_register_page_handler('forgotpassword', 'elgg_user_account_page_handler'); + elgg_register_page_handler('resetpassword', 'elgg_user_account_page_handler'); + elgg_register_page_handler('login', 'elgg_user_account_page_handler'); + elgg_register_page_handler('avatar', 'elgg_avatar_page_handler'); + elgg_register_page_handler('profile', 'elgg_profile_page_handler'); + elgg_register_page_handler('collections', 'collections_page_handler'); + + elgg_register_plugin_hook_handler('register', 'menu:user_hover', 'elgg_user_hover_menu'); + + elgg_register_action('register', '', 'public'); + elgg_register_action('useradd', '', 'admin'); + elgg_register_action('friends/add'); + elgg_register_action('friends/remove'); + elgg_register_action('avatar/upload'); + elgg_register_action('avatar/crop'); + elgg_register_action('avatar/remove'); + elgg_register_action('profile/edit'); + + elgg_register_action('friends/collections/add'); + elgg_register_action('friends/collections/delete'); + elgg_register_action('friends/collections/edit'); + + elgg_register_plugin_hook_handler('entity:icon:url', 'user', 'user_avatar_hook'); + + elgg_register_action('user/passwordreset', '', 'public'); + elgg_register_action('user/requestnewpassword', '', 'public'); + + elgg_register_widget_type('friends', elgg_echo('friends'), elgg_echo('friends:widget:description')); + + // Register the user type + elgg_register_entity_type('user', ''); + + elgg_register_plugin_hook_handler('register', 'menu:entity', 'elgg_users_setup_entity_menu', 501); + + elgg_register_event_handler('create', 'user', 'user_create_hook_add_site_relationship'); } /** * Runs unit tests for ElggObject * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params * * @return array + * @access private */ function users_test($hook, $type, $value, $params) { global $CONFIG; @@ -1548,6 +1605,7 @@ function users_test($hook, $type, $value, $params) { return $value; } -register_elgg_event_handler('init', 'system', 'users_init', 0); -register_elgg_event_handler('pagesetup', 'system', 'users_pagesetup', 0); -register_plugin_hook('unit_test', 'system', 'users_test');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'users_init', 0); +elgg_register_event_handler('init', 'system', 'elgg_profile_fields_setup', 10000); // Ensure this runs after other plugins +elgg_register_event_handler('pagesetup', 'system', 'users_pagesetup', 0); +elgg_register_plugin_hook_handler('unit_test', 'system', 'users_test'); diff --git a/engine/lib/usersettings.php b/engine/lib/usersettings.php deleted file mode 100644 index 396bb6de5..000000000 --- a/engine/lib/usersettings.php +++ /dev/null @@ -1,104 +0,0 @@ -<?php -/** - * Elgg user settings functions. - * Functions for adding and manipulating options on the user settings panel. - * - * @package Elgg.Core - * @subpackage Settings.User - */ - -/** - * Register a user settings page with the admin panel. - * This function extends the view "usersettings/main" with the provided view. - * This view should provide a description and either a control or a link to. - * - * Usage: - * - To add a control to the main admin panel then extend usersettings/main - * - To add a control to a new page create a page which renders a view - * usersettings/subpage (where subpage is your new page - - * nb. some pages already exist that you can extend), extend the main view - * to point to it, and add controls to your new view. - * - * At the moment this is essentially a wrapper around elgg_extend_view(). - * - * @param string $new_settings_view The view associated with the control you're adding - * @param string $view The view to extend, by default this is 'usersettings/main'. - * @param int $priority Optional priority to govern the appearance in the list. - * - * @return bool - */ -function extend_elgg_settings_page($new_settings_view, $view = 'usersettings/main', -$priority = 500) { - - return elgg_extend_view($view, $new_settings_view, $priority); -} - -/** - * Set up the page for user settings - * - * @return void - */ -function usersettings_pagesetup() { - // Get config - global $CONFIG; - - // Menu options - if (elgg_get_context() == "settings") { - $user = get_loggedin_user(); - add_submenu_item(elgg_echo('usersettings:user:opt:linktext'), - $CONFIG->wwwroot . "pg/settings/user/{$user->username}/"); - - add_submenu_item(elgg_echo('usersettings:plugins:opt:linktext'), - $CONFIG->wwwroot . "pg/settings/plugins/{$user->username}/"); - - add_submenu_item(elgg_echo('usersettings:statistics:opt:linktext'), - $CONFIG->wwwroot . "pg/settings/statistics/{$user->username}/"); - } -} - -/** - * Page handler for user settings - * - * @param array $page Pages array - * - * @return void - */ -function usersettings_page_handler($page) { - global $CONFIG; - - $path = $CONFIG->path . "pages/settings/index.php"; - - if ($page[0]) { - switch ($page[0]) { - case 'user': - $path = $CONFIG->path . "pages/settings/user.php"; - break; - case 'statistics': - $path = $CONFIG->path . "pages/settings/statistics.php"; - break; - case 'plugins': - $path = $CONFIG->path . "pages/settings/plugins.php"; - break; - } - } - - if ($page[1]) { - set_input('username', $page[1]); - } - - require($path); -} - -/** - * Initialise the admin page. - * - * @return void - */ -function usersettings_init() { - // Page handler - register_page_handler('settings', 'usersettings_page_handler'); -} - -/// Register init function -register_elgg_event_handler('init', 'system', 'usersettings_init'); -register_elgg_event_handler('pagesetup', 'system', 'usersettings_pagesetup');
\ No newline at end of file diff --git a/engine/lib/version.php b/engine/lib/version.php deleted file mode 100644 index 3e4e0e4f7..000000000 --- a/engine/lib/version.php +++ /dev/null @@ -1,134 +0,0 @@ -<?php -/** - * Elgg version library. - * Contains code for handling versioning and upgrades. - * - * @package Elgg.Core - * @subpackage Version - */ - -/** - * Run any php upgrade scripts which are required - * - * @param int $version Version upgrading from. - * @param bool $quiet Suppress errors. Don't use this. - * - * @return bool - */ -function upgrade_code($version, $quiet = FALSE) { - global $CONFIG; - - $version = (int) $version; - - if ($handle = opendir($CONFIG->path . 'engine/lib/upgrades/')) { - $upgrades = array(); - - while ($updatefile = readdir($handle)) { - // Look for upgrades and add to upgrades list - if (!is_dir($CONFIG->path . 'engine/lib/upgrades/' . $updatefile)) { - if (preg_match('/^([0-9]{10})\.(php)$/', $updatefile, $matches)) { - $core_version = (int) $matches[1]; - if ($core_version > $version) { - $upgrades[] = $updatefile; - } - } - } - } - - // Sort and execute - asort($upgrades); - - if (sizeof($upgrades) > 0) { - foreach ($upgrades as $upgrade) { - // hide all errors. - if ($quiet) { - // hide include errors as well as any exceptions that might happen - try { - if (!@include($CONFIG->path . 'engine/lib/upgrades/' . $upgrade)) { - error_log($e->getmessage()); - } - } catch (Exception $e) { - error_log($e->getmessage()); - } - } else { - include($CONFIG->path . 'engine/lib/upgrades/' . $upgrade); - } - } - } - - return TRUE; - } - - return FALSE; -} - -/** - * Get the current version information - * - * @param bool $humanreadable Whether to return a human readable version (default: false) - * - * @return string|false Depending on success - */ -function get_version($humanreadable = false) { - global $CONFIG; - - if (include($CONFIG->path . "version.php")) { - return (!$humanreadable) ? $version : $release; - } - - return FALSE; -} - -/** - * Determines whether or not the database needs to be upgraded. - * - * @return true|false Depending on whether or not the db version matches the code version - */ -function version_upgrade_check() { - $dbversion = (int) datalist_get('version'); - $version = get_version(); - - if ($version > $dbversion) { - return TRUE; - } - - return FALSE; -} - -/** - * Upgrades Elgg Database and code - * - * @return bool - * - */ -function version_upgrade() { - // It's possible large upgrades could exceed the max execution time. - set_time_limit(0); - - $dbversion = (int) datalist_get('version'); - - // No version number? Oh snap...this is an upgrade from a clean installation < 1.7. - // Run all upgrades without error reporting and hope for the best. - // See http://trac.elgg.org/elgg/ticket/1432 for more. - $quiet = !$dbversion; - - // Upgrade database - if (db_upgrade($dbversion, '', $quiet)) { - system_message(elgg_echo('upgrade:db')); - } - - // Upgrade core - if (upgrade_code($dbversion, $quiet)) { - system_message(elgg_echo('upgrade:core')); - } - - // Now we trigger an event to give the option for plugins to do something - $upgrade_details = new stdClass; - $upgrade_details->from = $dbversion; - $upgrade_details->to = get_version(); - - trigger_elgg_event('upgrade', 'upgrade', $upgrade_details); - - // Update the version - datalist_set('version', get_version()); -} diff --git a/engine/lib/views.php b/engine/lib/views.php index c70489117..1142461fe 100644 --- a/engine/lib/views.php +++ b/engine/lib/views.php @@ -40,7 +40,7 @@ * are called, so the init order doesn't affect views. * * @internal The file that determines the output of the view is the last - * registered by {@link set_view_location()}. + * registered by {@link elgg_set_view_location()}. * * @package Elgg.Core * @subpackage Views @@ -53,6 +53,7 @@ * @global string $CURRENT_SYSTEM_VIEWTYPE * @see elgg_set_viewtype() */ +global $CURRENT_SYSTEM_VIEWTYPE; $CURRENT_SYSTEM_VIEWTYPE = ""; /** @@ -100,12 +101,15 @@ function elgg_get_viewtype() { return $CURRENT_SYSTEM_VIEWTYPE; } - $viewtype = get_input('view', NULL); - if ($viewtype) { - return $viewtype; + $viewtype = get_input('view', '', false); + if (is_string($viewtype) && $viewtype !== '') { + // only word characters allowed. + if (!preg_match('/\W/', $viewtype)) { + return $viewtype; + } } - if (isset($CONFIG->view) && !empty($CONFIG->view)) { + if (!empty($CONFIG->view)) { return $CONFIG->view; } @@ -113,6 +117,45 @@ function elgg_get_viewtype() { } /** + * Register a view type as valid. + * + * @param string $view_type The view type to register + * @return bool + */ +function elgg_register_viewtype($view_type) { + global $CONFIG; + + if (!isset($CONFIG->view_types) || !is_array($CONFIG->view_types)) { + $CONFIG->view_types = array(); + } + + if (!in_array($view_type, $CONFIG->view_types)) { + $CONFIG->view_types[] = $view_type; + } + + return true; +} + +/** + * Checks if $view_type is valid on this installation. + * + * @param string $view_type View type + * + * @return bool + * @since 1.7.2 + * @access private + */ +function elgg_is_valid_view_type($view_type) { + global $CONFIG; + + if (!isset($CONFIG->view_types) || !is_array($CONFIG->view_types)) { + return FALSE; + } + + return in_array($view_type, $CONFIG->view_types); +} + +/** * Register a viewtype to fall back to a default view if a view isn't * found for that viewtype. * @@ -156,6 +199,37 @@ function elgg_does_viewtype_fallback($viewtype) { return FALSE; } +/** + * Register a view to be available for ajax calls + * + * @param string $view The view name + * @return void + * @since 1.8.3 + */ +function elgg_register_ajax_view($view) { + global $CONFIG; + + if (!isset($CONFIG->allowed_ajax_views)) { + $CONFIG->allowed_ajax_views = array(); + } + + $CONFIG->allowed_ajax_views[$view] = true; +} + +/** + * Unregister a view for ajax calls + * + * @param string $view The view name + * @return void + * @since 1.8.3 + */ +function elgg_unregister_ajax_view($view) { + global $CONFIG; + + if (isset($CONFIG->allowed_ajax_views[$view])) { + unset($CONFIG->allowed_ajax_views[$view]); + } +} /** * Returns the file location for a view. @@ -184,6 +258,94 @@ function elgg_get_view_location($view, $viewtype = '') { } else { return $CONFIG->views->locations[$viewtype][$view]; } +} + +/** + * Set an alternative base location for a view. + * + * Views are expected to be in plugin_name/views/. This function can + * be used to change that location. + * + * @internal Core view locations are stored in $CONFIG->viewpath. + * + * @tip This is useful to optionally register views in a plugin. + * + * @param string $view The name of the view + * @param string $location The base location path + * @param string $viewtype The view type + * + * @return void + */ +function elgg_set_view_location($view, $location, $viewtype = '') { + global $CONFIG; + + if (empty($viewtype)) { + $viewtype = 'default'; + } + + if (!isset($CONFIG->views)) { + $CONFIG->views = new stdClass; + } + + if (!isset($CONFIG->views->locations)) { + $CONFIG->views->locations = array($viewtype => array($view => $location)); + + } else if (!isset($CONFIG->views->locations[$viewtype])) { + $CONFIG->views->locations[$viewtype] = array($view => $location); + + } else { + $CONFIG->views->locations[$viewtype][$view] = $location; + } +} + +/** + * Returns whether the specified view exists + * + * @note If $recurse is true, also checks if a view exists only as an extension. + * + * @param string $view The view name + * @param string $viewtype If set, forces the viewtype + * @param bool $recurse If false, do not check extensions + * + * @return bool + */ +function elgg_view_exists($view, $viewtype = '', $recurse = true) { + global $CONFIG; + + // Detect view type + if (empty($viewtype)) { + $viewtype = elgg_get_viewtype(); + } + + if (!isset($CONFIG->views->locations[$viewtype][$view])) { + if (!isset($CONFIG->viewpath)) { + $location = dirname(dirname(dirname(__FILE__))) . "/views/"; + } else { + $location = $CONFIG->viewpath; + } + } else { + $location = $CONFIG->views->locations[$viewtype][$view]; + } + + if (file_exists("{$location}{$viewtype}/{$view}.php")) { + return true; + } + + // If we got here then check whether this exists as an extension + // We optionally recursively check whether the extended view exists also for the viewtype + if ($recurse && isset($CONFIG->views->extensions[$view])) { + foreach ($CONFIG->views->extensions[$view] as $view_extension) { + // do not recursively check to stay away from infinite loops + if (elgg_view_exists($view_extension, $viewtype, false)) { + return true; + } + } + } + + // Now check if the default view exists if the view is registered as a fallback + if ($viewtype != 'default' && elgg_does_viewtype_fallback($viewtype)) { + return elgg_view_exists($view, 'default'); + } return false; } @@ -194,11 +356,12 @@ function elgg_get_view_location($view, $viewtype = '') { * Views are rendered by a template handler and returned as strings. * * Views are called with a special $vars variable set, - * which includes any variables passed as the second parameter, - * as well as some defaults: - * - All $_SESSION vars merged to $vars array. - * - $vars['config'] The $CONFIG global. (Use {@link get_config()} instead). - * - $vars['url'] The site URL. + * which includes any variables passed as the second parameter. + * For backward compatbility, the following variables are also set but we + * recommend that you do not use them: + * - $vars['config'] The $CONFIG global. (Use {@link elgg_get_config()} instead). + * - $vars['url'] The site URL. (use {@link elgg_get_site_url()} instead). + * - $vars['user'] The logged in user. (use {@link elgg_get_logged_in_user_entity()} instead). * * Custom template handlers can be set with {@link set_template_handler()}. * @@ -206,14 +369,14 @@ function elgg_get_view_location($view, $viewtype = '') { * view, $view_name plugin hook. * * @warning Any variables in $_SESSION will override passed vars - * upon name collision. See {@trac #2124}. + * upon name collision. See https://github.com/Elgg/Elgg/issues/2124 * * @param string $view The name and location of the view to use * @param array $vars Variables to pass to the view. * @param boolean $bypass If set to true, elgg_view will bypass any specified * alternative template handler; by default, it will * hand off to this if requested (see set_template_handler) - * @param boolean $debug If set to true, the viewer will complain if it can't find a view + * @param boolean $ignored This argument is ignored and will be removed eventually * @param string $viewtype If set, forces the viewtype for the elgg_view call to be * this value (default: standard detection) * @@ -221,95 +384,94 @@ function elgg_get_view_location($view, $viewtype = '') { * @see set_template_handler() * @example views/elgg_view.php * @link http://docs.elgg.org/View - * @todo $debug isn't used. - * @todo $usercache is redundant. */ -function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $viewtype = '') { +function elgg_view($view, $vars = array(), $bypass = false, $ignored = false, $viewtype = '') { global $CONFIG; - static $usercache; - - $view = (string)$view; + if (!is_string($view) || !is_string($viewtype)) { + elgg_log("View and Viewtype in views must be a strings: $view", 'NOTICE'); + return ''; + } // basic checking for bad paths if (strpos($view, '..') !== false) { - return false; - } - - $view_orig = $view; - - // Trigger the pagesetup event - if (!isset($CONFIG->pagesetupdone)) { - trigger_elgg_event('pagesetup', 'system'); - $CONFIG->pagesetupdone = true; - } - - if (!is_array($usercache)) { - $usercache = array(); + return ''; } if (!is_array($vars)) { - elgg_log('Vars in views must be an array!', 'ERROR'); + elgg_log("Vars in views must be an array: $view", 'ERROR'); $vars = array(); } - if (empty($vars)) { - $vars = array(); + // Get the current viewtype + if ($viewtype === '') { + $viewtype = elgg_get_viewtype(); + } elseif (preg_match('/\W/', $viewtype)) { + // Viewtypes can only be alphanumeric + return ''; } - // Load session and configuration variables into $vars - if (isset($_SESSION)) { - $vars += $_SESSION; - } + $view_orig = $view; - $vars['config'] = array(); + // Trigger the pagesetup event + if (!isset($CONFIG->pagesetupdone) && $CONFIG->boot_complete) { + $CONFIG->pagesetupdone = true; + elgg_trigger_event('pagesetup', 'system'); + } - if (!empty($CONFIG)) { + // @warning - plugin authors: do not expect user, config, and url to be + // set by elgg_view() in the future. Instead, use elgg_get_logged_in_user_entity(), + // elgg_get_config(), and elgg_get_site_url() in your views. + if (!isset($vars['user'])) { + $vars['user'] = elgg_get_logged_in_user_entity(); + } + if (!isset($vars['config'])) { $vars['config'] = $CONFIG; } + if (!isset($vars['url'])) { + $vars['url'] = elgg_get_site_url(); + } - $vars['url'] = elgg_get_site_url(); - - // Load page owner variables into $vars - if (is_callable('page_owner')) { - $vars['page_owner'] = elgg_get_page_owner_guid(); - } else { - $vars['page_owner'] = -1; + // full_view is the new preferred key for full view on entities @see elgg_view_entity() + // check if full_view is set because that means we've already rewritten it and this is + // coming from another view passing $vars directly. + if (isset($vars['full']) && !isset($vars['full_view'])) { + elgg_deprecated_notice("Use \$vars['full_view'] instead of \$vars['full']", 1.8, 2); + $vars['full_view'] = $vars['full']; + } + if (isset($vars['full_view'])) { + $vars['full'] = $vars['full_view']; } - // @todo why is is_installed() here? - if (($vars['page_owner'] != -1) && (is_installed())) { - if (!isset($usercache[$vars['page_owner']])) { - $vars['page_owner_user'] = get_entity($vars['page_owner']); - $usercache[$vars['page_owner']] = $vars['page_owner_user']; - } else { - $vars['page_owner_user'] = $usercache[$vars['page_owner']]; + // internalname => name (1.8) + if (isset($vars['internalname']) && !isset($vars['__ignoreInternalname']) && !isset($vars['name'])) { + elgg_deprecated_notice('You should pass $vars[\'name\'] now instead of $vars[\'internalname\']', 1.8, 2); + $vars['name'] = $vars['internalname']; + } elseif (isset($vars['name'])) { + if (!isset($vars['internalname'])) { + $vars['__ignoreInternalname'] = ''; } + $vars['internalname'] = $vars['name']; } - // @todo why is there a special js var here? - // is this just for input views that could accept js as a param? - if (!isset($vars['js'])) { - $vars['js'] = ""; + // internalid => id (1.8) + if (isset($vars['internalid']) && !isset($vars['__ignoreInternalid']) && !isset($vars['name'])) { + elgg_deprecated_notice('You should pass $vars[\'id\'] now instead of $vars[\'internalid\']', 1.8, 2); + $vars['id'] = $vars['internalid']; + } elseif (isset($vars['id'])) { + if (!isset($vars['internalid'])) { + $vars['__ignoreInternalid'] = ''; + } + $vars['internalid'] = $vars['id']; } // If it's been requested, pass off to a template handler instead if ($bypass == false && isset($CONFIG->template_handler) && !empty($CONFIG->template_handler)) { $template_handler = $CONFIG->template_handler; if (is_callable($template_handler)) { - return $template_handler($view, $vars); + return call_user_func($template_handler, $view, $vars); } } - // Get the current viewtype - if (empty($viewtype)) { - $viewtype = elgg_get_viewtype(); - } - - // Viewtypes can only be alphanumeric - if (preg_match('[\W]', $viewtype)) { - return ''; - } - // Set up any extensions to the requested view if (isset($CONFIG->views->extensions[$view])) { $viewlist = $CONFIG->views->extensions[$view]; @@ -321,19 +483,21 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie ob_start(); foreach ($viewlist as $priority => $view) { + $view_location = elgg_get_view_location($view, $viewtype); $view_file = "$view_location$viewtype/$view.php"; - $default_location = elgg_get_view_location($view, 'default'); - $default_view_file = "{$default_location}default/$view.php"; - // try to include view if (!file_exists($view_file) || !include($view_file)) { // requested view does not exist $error = "$viewtype/$view view does not exist."; // attempt to load default view - if ($viewtype != 'default' && elgg_does_viewtype_fallback($viewtype)) { + if ($viewtype !== 'default' && elgg_does_viewtype_fallback($viewtype)) { + + $default_location = elgg_get_view_location($view, 'default'); + $default_view_file = "{$default_location}default/$view.php"; + if (file_exists($default_view_file) && include($default_view_file)) { // default view found $error .= " Using default/$view instead."; @@ -352,336 +516,268 @@ function elgg_view($view, $vars = array(), $bypass = false, $debug = false, $vie $content = ob_get_clean(); // Plugin hook - $content = trigger_plugin_hook('view', $view_orig, - array('view' => $view_orig, 'vars' => $vars), $content); + $params = array('view' => $view_orig, 'vars' => $vars, 'viewtype' => $viewtype); + $content = elgg_trigger_plugin_hook('view', $view_orig, $params, $content); - // backward compatibility with less grandular hook will be gone in 2.0 - $params = array('view' => $view_orig, 'vars' => $vars); - $content_tmp = trigger_plugin_hook('display', 'view', $params, $content); + // backward compatibility with less granular hook will be gone in 2.0 + $content_tmp = elgg_trigger_plugin_hook('display', 'view', $params, $content); - if ($content_tmp != $content) { + if ($content_tmp !== $content) { $content = $content_tmp; - elgg_deprecated_notice('The display:view plugin hook is deprecated by view:view_name or view:all', 1.8); + elgg_deprecated_notice('The display:view plugin hook is deprecated by view:view_name', 1.8); } return $content; } /** - * Returns whether the specified view exists - * - * @note If $recurse is strue, also checks if a view exists only as an extension. - * - * @param string $view The view name - * @param string $viewtype If set, forces the viewtype - * @param bool $recurse If false, do not check extensions + * Extends a view with another view. * - * @return bool - */ -function elgg_view_exists($view, $viewtype = '', $recurse = true) { - global $CONFIG; - - // Detect view type - if (empty($viewtype)) { - $viewtype = elgg_get_viewtype(); - } - - if (!isset($CONFIG->views->locations[$viewtype][$view])) { - if (!isset($CONFIG->viewpath)) { - $location = dirname(dirname(dirname(__FILE__))) . "/views/"; - } else { - $location = $CONFIG->viewpath; - } - } else { - $location = $CONFIG->views->locations[$viewtype][$view]; - } - - if (file_exists($location . "{$viewtype}/{$view}.php")) { - return true; - } - - // If we got here then check whether this exists as an extension - // We optionally recursively check whether the extended view exists also for the viewtype - if ($recurse && isset($CONFIG->views->extensions[$view])) { - foreach ($CONFIG->views->extensions[$view] as $view_extension) { - // do not recursively check to stay away from infinite loops - if (elgg_view_exists($view_extension, $viewtype, false)) { - return true; - } - } - } - - return false; -} - -/** - * Registers a view to simple cache. + * The output of any view can be prepended or appended to any other view. * - * Simple cache is a caching mechanism that saves the output of - * views and its extensions into a file. If the view is called - * by the {@link simplecache/view.php} file, the Elgg framework will - * not be loaded and the contents of the view will returned - * from file. + * The default action is to append a view. If the priority is less than 500, + * the output of the extended view will be appended to the original view. * - * @warning Simple cached views must take no parameters and return - * the same content no matter who is logged in. + * Priority can be specified and affects the order in which extensions + * are appended or prepended. * - * @note CSS and the basic JS views are automatically cached. + * @internal View extensions are stored in + * $CONFIG->views->extensions[$view][$priority] = $view_extension * - * @param string $viewname View name + * @param string $view The view to extend. + * @param string $view_extension This view is added to $view + * @param int $priority The priority, from 0 to 1000, + * to add at (lowest numbers displayed first) * * @return void - * @link http://docs.elgg.org/Views/Simplecache - * @see elgg_view_regenerate_simplecache() + * @since 1.7.0 + * @link http://docs.elgg.org/Views/Extend + * @example views/extend.php */ -function elgg_view_register_simplecache($viewname) { +function elgg_extend_view($view, $view_extension, $priority = 501) { global $CONFIG; if (!isset($CONFIG->views)) { - $CONFIG->views = new stdClass; + $CONFIG->views = (object) array( + 'extensions' => array(), + ); + $CONFIG->views->extensions[$view][500] = (string)$view; + } else { + if (!isset($CONFIG->views->extensions[$view])) { + $CONFIG->views->extensions[$view][500] = (string)$view; + } } - if (!isset($CONFIG->views->simplecache)) { - $CONFIG->views->simplecache = array(); + // raise priority until it doesn't match one already registered + while (isset($CONFIG->views->extensions[$view][$priority])) { + $priority++; } - $CONFIG->views->simplecache[] = $viewname; + $CONFIG->views->extensions[$view][$priority] = (string)$view_extension; + ksort($CONFIG->views->extensions[$view]); } /** - * Regenerates the simple cache. - * - * @warning This does not invalidate the cache, but actively resets it. + * Unextends a view. * - * @param string $viewtype Optional viewtype to regenerate + * @param string $view The view that was extended. + * @param string $view_extension This view that was added to $view * - * @return void - * @see elgg_view_register_simplecache() + * @return bool + * @since 1.7.2 */ -function elgg_view_regenerate_simplecache($viewtype = NULL) { +function elgg_unextend_view($view, $view_extension) { global $CONFIG; - if (!isset($CONFIG->views->simplecache) || !is_array($CONFIG->views->simplecache)) { - return; - } - - $lastcached = time(); - - // @todo elgg_view() checks if the page set is done (isset($CONFIG->pagesetupdone)) and - // triggers an event if it's not. Calling elgg_view() here breaks submenus - // (at least) because the page setup hook is called before any - // contexts can be correctly set (since this is called before page_handler()). - // To avoid this, lie about $CONFIG->pagehandlerdone to force - // the trigger correctly when the first view is actually being output. - $CONFIG->pagesetupdone = TRUE; - - if (!file_exists($CONFIG->dataroot . 'views_simplecache')) { - mkdir($CONFIG->dataroot . 'views_simplecache'); - } - - if (isset($viewtype)) { - $viewtypes = array($viewtype); - } else { - $viewtypes = $CONFIG->view_types; + if (!isset($CONFIG->views->extensions[$view])) { + return FALSE; } - $original_viewtype = elgg_get_viewtype(); - - foreach ($viewtypes as $viewtype) { - elgg_set_viewtype($viewtype); - foreach ($CONFIG->views->simplecache as $view) { - $viewcontents = elgg_view($view); - $viewname = md5(elgg_get_viewtype() . $view); - if ($handle = fopen($CONFIG->dataroot . 'views_simplecache/' . $viewname, 'w')) { - fwrite($handle, $viewcontents); - fclose($handle); - } - } - - datalist_set("simplecache_lastupdate_$viewtype", $lastcached); - datalist_set("simplecache_lastcached_$viewtype", $lastcached); + $priority = array_search($view_extension, $CONFIG->views->extensions[$view]); + if ($priority === FALSE) { + return FALSE; } - elgg_set_viewtype($original_viewtype); - - // needs to be set for links in html head - $CONFIG->lastcache = $lastcached; + unset($CONFIG->views->extensions[$view][$priority]); - unset($CONFIG->pagesetupdone); + return TRUE; } /** - * Enables the simple cache. + * Assembles and outputs a full page. * - * @access private - * @see elgg_view_register_simplecache() - * @return void - */ -function elgg_view_enable_simplecache() { - global $CONFIG; - - datalist_set('simplecache_enabled', 1); - $CONFIG->simplecache_enabled = 1; - elgg_view_regenerate_simplecache(); -} - -/** - * Disables the simple cache. + * A "page" in Elgg is determined by the current view type and + * can be HTML for a browser, RSS for a feed reader, or + * Javascript, PHP and a number of other formats. * - * @warning Simplecache is also purged when disabled. + * @param string $title Title + * @param string $body Body + * @param string $page_shell Optional page shell to use. See page/shells view directory + * @param array $vars Optional vars array to pass to the page + * shell. Automatically adds title, body, and sysmessages * - * @access private - * @see elgg_view_register_simplecache() - * @return void + * @return string The contents of the page + * @since 1.8 */ -function elgg_view_disable_simplecache() { - global $CONFIG; - if ($CONFIG->simplecache_enabled) { - datalist_set('simplecache_enabled', 0); - $CONFIG->simplecache_enabled = 0; - - // purge simple cache - if ($handle = opendir($CONFIG->dataroot . 'views_simplecache')) { - while (false !== ($file = readdir($handle))) { - if ($file != "." && $file != "..") { - unlink($CONFIG->dataroot . 'views_simplecache/' . $file); - } - } - closedir($handle); +function elgg_view_page($title, $body, $page_shell = 'default', $vars = array()) { + + $messages = null; + if (count_messages()) { + // get messages - try for errors first + $messages = system_messages(NULL, "error"); + if (count($messages["error"]) == 0) { + // no errors so grab rest of messages + $messages = system_messages(null, ""); + } else { + // we have errors - clear out remaining messages + system_messages(null, ""); } } -} - -/** - * Invalidates all cached views in the simplecache - * - * @return bool - * @since 1.7.4 - */ -function elgg_invalidate_simplecache() { - global $CONFIG; - - $return = TRUE; - if ($handle = opendir($CONFIG->dataroot . 'views_simplecache')) { - while (false !== ($file = readdir($handle))) { - if ($file != "." && $file != "..") { - $return = $return && unlink($CONFIG->dataroot . 'views_simplecache/' . $file); - } - } - closedir($handle); + $vars['title'] = $title; + $vars['body'] = $body; + $vars['sysmessages'] = $messages; + + $vars = elgg_trigger_plugin_hook('output:before', 'page', null, $vars); + + // check for deprecated view + if ($page_shell == 'default' && elgg_view_exists('pageshells/pageshell')) { + elgg_deprecated_notice("pageshells/pageshell is deprecated by page/$page_shell", 1.8); + $output = elgg_view('pageshells/pageshell', $vars); } else { - $return = FALSE; + $output = elgg_view("page/$page_shell", $vars); } - return $return; + $vars['page_shell'] = $page_shell; + + // Allow plugins to mod output + return elgg_trigger_plugin_hook('output', 'page', $vars, $output); } /** - * Returns the name of views for in a directory. - * - * Use this to get all namespaced views under the first element. + * Displays a layout with optional parameters. * - * @param string $dir The main directory that holds the views. (mod/profile/views/) - * @param string $base The root name of the view to use, without the viewtype. (profile) + * Layouts provide consistent organization of pages and other blocks of content. + * There are a few default layouts in core: + * - admin A special layout for the admin area. + * - one_column A single content column. + * - one_sidebar A content column with sidebar. + * - two_sidebar A content column with two sidebars. + * - widgets A widget canvas. + * + * The layout views take the form page/layouts/$layout_name + * See the individual layouts for what options are supported. The three most + * common layouts have these parameters: + * one_column + * content => string + * one_sidebar + * content => string + * sidebar => string (optional) + * content + * content => string + * sidebar => string (optional) + * buttons => string (override the default add button) + * title => string (override the default title) + * filter_context => string (selected content filter) + * See the content layout view for more parameters + * + * @param string $layout_name The name of the view in page/layouts/. + * @param array $vars Associative array of parameters for the layout view * - * @return array - * @since 1.7.0 - * @todo Why isn't this used anywhere else but in elgg_view_tree()? - * Seems like a useful function for autodiscovery. + * @return string The layout */ -function elgg_get_views($dir, $base) { - $return = array(); - if (file_exists($dir) && is_dir($dir)) { - if ($handle = opendir($dir)) { - while ($view = readdir($handle)) { - if (!in_array($view, array('.', '..', '.svn', 'CVS'))) { - if (is_dir($dir . '/' . $view)) { - if ($val = elgg_get_views($dir . '/' . $view, $base . '/' . $view)) { - $return = array_merge($return, $val); - } - } else { - $view = str_replace('.php', '', $view); - $return[] = $base . '/' . $view; - } - } - } +function elgg_view_layout($layout_name, $vars = array()) { + + if (is_string($vars) || $vars === null) { + elgg_deprecated_notice("The use of unlimited optional string arguments in elgg_view_layout() was deprecated in favor of an options array", 1.8); + $arg = 1; + $param_array = array(); + while ($arg < func_num_args()) { + $param_array['area' . $arg] = func_get_arg($arg); + $arg++; } + } else { + $param_array = $vars; } - return $return; -} + $params = elgg_trigger_plugin_hook('output:before', 'layout', null, $param_array); -/** - * Get views in a dir - * - * @deprecated 1.7. Use elgg_get_views(). - * - * @param string $dir Dir - * @param string $base Base view - * - * @return array - */ -function get_views($dir, $base) { - elgg_deprecated_notice('get_views() was deprecated by elgg_get_views()!', 1.7); - elgg_get_views($dir, $base); + // check deprecated location + if (elgg_view_exists("canvas/layouts/$layout_name")) { + elgg_deprecated_notice("canvas/layouts/$layout_name is deprecated by page/layouts/$layout_name", 1.8); + $output = elgg_view("canvas/layouts/$layout_name", $params); + } elseif (elgg_view_exists("page/layouts/$layout_name")) { + $output = elgg_view("page/layouts/$layout_name", $params); + } else { + $output = elgg_view("page/layouts/default", $params); + } + + return elgg_trigger_plugin_hook('output:after', 'layout', $params, $output); } /** - * Returns all views below a partial view. - * - * Settings $view_root = 'profile' will show all available views under - * the "profile" namespace. - * - * @param string $view_root The root view - * @param string $viewtype Optionally specify a view type - * other than the current one. + * Render a menu + * + * @see elgg_register_menu_item() for documentation on adding menu items and + * navigation.php for information on the different menus available. + * + * This function triggers a 'register', 'menu:<menu name>' plugin hook that enables + * plugins to add menu items just before a menu is rendered. This is used by + * dynamic menus (menus that change based on some input such as the user hover + * menu). Using elgg_register_menu_item() in response to the hook can cause + * incorrect links to show up. See the blog plugin's blog_owner_block_menu() + * for an example of using this plugin hook. + * + * An additional hook is the 'prepare', 'menu:<menu name>' which enables plugins + * to modify the structure of the menu (sort it, remove items, set variables on + * the menu items). + * + * elgg_view_menu() uses views in navigation/menu + * + * @param string $menu_name The name of the menu + * @param array $vars An associative array of display options for the menu. + * Options include: + * sort_by => string or php callback + * string options: 'name', 'priority', 'title' (default), + * 'register' (registration order) or a + * php callback (a compare function for usort) + * handler: string the page handler to build action URLs + * entity: ElggEntity to use to build action URLs + * class: string the class for the entire menu. + * show_section_headers: bool show headers before menu sections. * - * @return array A list of view names underneath that root view - * @todo This is used once in the deprecated get_activity_stream_data() function. + * @return string + * @since 1.8.0 */ -function elgg_view_tree($view_root, $viewtype = "") { +function elgg_view_menu($menu_name, array $vars = array()) { global $CONFIG; - static $treecache; - // Get viewtype - if (!$viewtype) { - $viewtype = elgg_get_viewtype(); - } + $vars['name'] = $menu_name; - // Has the treecache been initialised? - if (!isset($treecache)) { - $treecache = array(); - } - // A little light internal caching - if (!empty($treecache[$view_root])) { - return $treecache[$view_root]; - } + $sort_by = elgg_extract('sort_by', $vars, 'text'); - // Examine $CONFIG->views->locations - if (isset($CONFIG->views->locations[$viewtype])) { - foreach ($CONFIG->views->locations[$viewtype] as $view => $path) { - $pos = strpos($view, $view_root); - if ($pos === 0) { - $treecache[$view_root][] = $view; - } - } + if (isset($CONFIG->menus[$menu_name])) { + $menu = $CONFIG->menus[$menu_name]; + } else { + $menu = array(); } - // Now examine core - $location = $CONFIG->viewpath; - $viewtype = elgg_get_viewtype(); - $root = $location . $viewtype . '/' . $view_root; + // Give plugins a chance to add menu items just before creation. + // This supports dynamic menus (example: user_hover). + $menu = elgg_trigger_plugin_hook('register', "menu:$menu_name", $vars, $menu); - if (file_exists($root) && is_dir($root)) { - $val = elgg_get_views($root, $view_root); - if (!is_array($treecache[$view_root])) { - $treecache[$view_root] = array(); - } - $treecache[$view_root] = array_merge($treecache[$view_root], $val); - } + $builder = new ElggMenuBuilder($menu); + $vars['menu'] = $builder->getMenu($sort_by); + $vars['selected_item'] = $builder->getSelected(); - return $treecache[$view_root]; + // Let plugins modify the menu + $vars['menu'] = elgg_trigger_plugin_hook('prepare', "menu:$menu_name", $vars, $vars['menu']); + + if (elgg_view_exists("navigation/menu/$menu_name")) { + return elgg_view("navigation/menu/$menu_name", $vars); + } else { + return elgg_view("navigation/menu/default", $vars); + } } /** @@ -694,14 +790,17 @@ function elgg_view_tree($view_root, $viewtype = "") { * * The entity view is called with the following in $vars: * - ElggEntity 'entity' The entity being viewed - * - bool 'full' Whether to show a full or condensed view. + * + * Other common view $vars paramters: + * - bool 'full_view' Whether to show a full or condensed view. * * @tip This function can automatically appends annotations to entities if in full - * view and a handler is registered for the entity:annotate. See {@trac 964} and + * view and a handler is registered for the entity:annotate. See https://github.com/Elgg/Elgg/issues/964 and * {@link elgg_view_entity_annotations()}. * * @param ElggEntity $entity The entity to display - * @param boolean $full Passed to entity view to decide how much information to show. + * @param array $vars Array of variables to pass to the entity view. + * In Elgg 1.7 and earlier it was the boolean $full_view * @param boolean $bypass If false, will not pass to a custom template handler. * {@see set_template_handler()} * @param boolean $debug Complain if views are missing @@ -711,51 +810,56 @@ function elgg_view_tree($view_root, $viewtype = "") { * @link http://docs.elgg.org/Entities * @todo The annotation hook might be better as a generic plugin hook to append content. */ -function elgg_view_entity(ElggEntity $entity, $full = false, $bypass = true, $debug = false) { - global $autofeed; - $autofeed = true; +function elgg_view_entity(ElggEntity $entity, $vars = array(), $bypass = true, $debug = false) { // No point continuing if entity is null - if (!$entity) { - return ''; + if (!$entity || !($entity instanceof ElggEntity)) { + return false; } - if (!($entity instanceof ElggEntity)) { - return false; + global $autofeed; + $autofeed = true; + + $defaults = array( + 'full_view' => false, + ); + + if (is_array($vars)) { + $vars = array_merge($defaults, $vars); + } else { + elgg_deprecated_notice("Update your use of elgg_view_entity()", 1.8); + $vars = array( + 'full_view' => $vars, + ); } + $vars['entity'] = $entity; + + // if this entity has a view defined, use it $view = $entity->view; if (is_string($view)) { - return elgg_view($view, - array('entity' => $entity, 'full' => $full), - $bypass, - $debug); + return elgg_view($view, $vars, $bypass, $debug); } $entity_type = $entity->getType(); $subtype = $entity->getSubtype(); if (empty($subtype)) { - $subtype = $entity_type; + $subtype = 'default'; } $contents = ''; - if (elgg_view_exists("{$entity_type}/{$subtype}")) { - $contents = elgg_view("{$entity_type}/{$subtype}", array( - 'entity' => $entity, - 'full' => $full - ), $bypass, $debug); + if (elgg_view_exists("$entity_type/$subtype")) { + $contents = elgg_view("$entity_type/$subtype", $vars, $bypass, $debug); } if (empty($contents)) { - $contents = elgg_view("{$entity_type}/default", array( - 'entity' => $entity, - 'full' => $full - ), $bypass, $debug); + $contents = elgg_view("$entity_type/default", $vars, $bypass, $debug); } + // Marcus Povey 20090616 : Speculative and low impact approach for fixing #964 - if ($full) { - $annotations = elgg_view_entity_annotations($entity, $full); + if ($vars['full_view']) { + $annotations = elgg_view_entity_annotations($entity, $vars['full_view']); if ($annotations) { $contents .= $annotations; @@ -765,6 +869,52 @@ function elgg_view_entity(ElggEntity $entity, $full = false, $bypass = true, $de } /** + * View the icon of an entity + * + * Entity views are determined by having a view named after the entity $type/$subtype. + * Entities that do not have a defined icon/$type/$subtype view will fall back to using + * the icon/$type/default view. + * + * @param ElggEntity $entity The entity to display + * @param string $size The size: tiny, small, medium, large + * @param array $vars An array of variables to pass to the view. Some possible + * variables are img_class and link_class. See the + * specific icon view for more parameters. + * + * @return string HTML to display or false + */ +function elgg_view_entity_icon(ElggEntity $entity, $size = 'medium', $vars = array()) { + + // No point continuing if entity is null + if (!$entity || !($entity instanceof ElggEntity)) { + return false; + } + + $vars['entity'] = $entity; + $vars['size'] = $size; + + $entity_type = $entity->getType(); + + $subtype = $entity->getSubtype(); + if (empty($subtype)) { + $subtype = 'default'; + } + + $contents = ''; + if (elgg_view_exists("icon/$entity_type/$subtype")) { + $contents = elgg_view("icon/$entity_type/$subtype", $vars); + } + if (empty($contents)) { + $contents = elgg_view("icon/$entity_type/default", $vars); + } + if (empty($contents)) { + $contents = elgg_view("icon/default", $vars); + } + + return $contents; +} + +/** * Returns a string of a rendered annotation. * * Annotation views are expected to be in annotation/$annotation_name. @@ -777,131 +927,157 @@ function elgg_view_entity(ElggEntity $entity, $full = false, $bypass = true, $de * - ElggEntity 'annotation' The annotation being viewed. * * @param ElggAnnotation $annotation The annotation to display - * @param boolean $bypass If false, will not pass to a custom + * @param array $vars Variable array for view. + * @param bool $bypass If false, will not pass to a custom * template handler. {@see set_template_handler()} - * @param boolean $debug Complain if views are missing + * @param bool $debug Complain if views are missing * - * @return string HTML (etc) to display + * @return string/false Rendered annotation */ -function elgg_view_annotation(ElggAnnotation $annotation, $bypass = true, $debug = false) { +function elgg_view_annotation(ElggAnnotation $annotation, array $vars = array(), $bypass = true, $debug = false) { global $autofeed; $autofeed = true; + $defaults = array( + 'full_view' => true, + ); + + $vars = array_merge($defaults, $vars); + $vars['annotation'] = $annotation; + + // @todo setting the view on an annotation is not advertised anywhere + // do we want to keep this? $view = $annotation->view; if (is_string($view)) { - return elgg_view($view, array('annotation' => $annotation), $bypass, $debug); + return elgg_view($view, $vars, $bypass, $debug); } $name = $annotation->name; - $intname = (int) $name; - if ("{$intname}" == "{$name}") { - $name = get_metastring($intname); - } if (empty($name)) { - return ""; + return false; } - if (elgg_view_exists("annotation/{$name}")) { - return elgg_view("annotation/{$name}", array('annotation' => $annotation), $bypass, $debug); + if (elgg_view_exists("annotation/$name")) { + return elgg_view("annotation/$name", $vars, $bypass, $debug); } else { - return elgg_view("annotation/default", array('annotation' => $annotation), $bypass, $debug); + return elgg_view("annotation/default", $vars, $bypass, $debug); } } - /** * Returns a rendered list of entities with pagination. This function should be * called by wrapper functions. * - * @see list_entities() - * @see list_user_objects() + * @see elgg_list_entities() * @see list_user_friends_objects() - * @see list_entities_from_metadata() - * @see list_entities_from_metadata_multi() - * @see list_entities_from_relationships() - * @see list_site_members() - * - * @param array $entities List of entities - * @param int $count The total number of entities across all pages - * @param int $offset The current indexing offset - * @param int $limit The number of entities to display per page - * @param bool $fullview Whether or not to display the full view (default: true) - * @param bool $viewtypetoggle Whether or not to allow users to toggle to gallery view - * @param bool $pagination Whether pagination is offered. - * - * @return string The list of entities + * @see elgg_list_entities_from_metadata() + * @see elgg_list_entities_from_relationships() + * @see elgg_list_entities_from_annotations() + * + * @param array $entities Array of entities + * @param array $vars Display variables + * 'count' The total number of entities across all pages + * 'offset' The current indexing offset + * 'limit' The number of entities to display per page + * 'full_view' Display the full view of the entities? + * 'list_class' CSS class applied to the list + * 'item_class' CSS class applied to the list items + * 'pagination' Display pagination? + * 'list_type' List type: 'list' (default), 'gallery' + * 'list_type_toggle' Display the list type toggle? + * + * @return string The rendered list of entities * @access private */ -function elgg_view_entity_list($entities, $count, $offset, $limit, $fullview = true, -$viewtypetoggle = true, $pagination = true) { - - $count = (int) $count; - $limit = (int) $limit; +function elgg_view_entity_list($entities, $vars = array(), $offset = 0, $limit = 10, $full_view = true, +$list_type_toggle = true, $pagination = true) { + + if (!$vars["limit"] && !$vars["offset"]) { + // no need for pagination if listing is unlimited
+ $vars["pagination"] = false;
+ }
+ + if (!is_int($offset)) { + $offset = (int)get_input('offset', 0); + } + + // list type can be passed as request parameter + $list_type = get_input('list_type', 'list'); + if (get_input('listtype')) { + elgg_deprecated_notice("'listtype' has been deprecated by 'list_type' for lists", 1.8); + $list_type = get_input('listtype'); + } + + if (is_array($vars)) { + // new function + $defaults = array( + 'items' => $entities, + 'list_class' => 'elgg-list-entity', + 'full_view' => true, + 'pagination' => true, + 'list_type' => $list_type, + 'list_type_toggle' => false, + 'offset' => $offset, + ); + + $vars = array_merge($defaults, $vars); - // do not require views to explicitly pass in the offset - if (!$offset = (int) $offset) { - $offset = sanitise_int(get_input('offset', 0)); + } else { + // old function parameters + elgg_deprecated_notice("Please update your use of elgg_view_entity_list()", 1.8); + + $vars = array( + 'items' => $entities, + 'count' => (int) $vars, // the old count parameter + 'offset' => $offset, + 'limit' => (int) $limit, + 'full_view' => $full_view, + 'pagination' => $pagination, + 'list_type' => $list_type, + 'list_type_toggle' => $list_type_toggle, + 'list_class' => 'elgg-list-entity', + ); + } + + if ($vars['list_type'] != 'list') { + return elgg_view('page/components/gallery', $vars); + } else { + return elgg_view('page/components/list', $vars); } - - $context = elgg_get_context(); - - $html = elgg_view('entities/entity_list', array( - 'entities' => $entities, - 'count' => $count, - 'offset' => $offset, - 'limit' => $limit, - 'baseurl' => $_SERVER['REQUEST_URI'], - 'fullview' => $fullview, - 'context' => $context, - 'viewtypetoggle' => $viewtypetoggle, - 'viewtype' => get_input('search_viewtype', 'list'), - 'pagination' => $pagination - )); - - return $html; } /** * Returns a rendered list of annotations, plus pagination. This function * should be called by wrapper functions. * - * @param array $annotations List of annotations - * @param int $count The total number of annotations across all pages - * @param int $offset The current indexing offset - * @param int $limit The number of annotations to display per page + * @param array $annotations Array of annotations + * @param array $vars Display variables + * 'count' The total number of annotations across all pages + * 'offset' The current indexing offset + * 'limit' The number of annotations to display per page + * 'full_view' Display the full view of the annotation? + * 'list_class' CSS Class applied to the list + * 'offset_key' The url parameter key used for offset * * @return string The list of annotations * @access private */ -function elgg_view_annotation_list($annotations, $count, $offset, $limit) { - $count = (int) $count; - $offset = (int) $offset; - $limit = (int) $limit; - - $html = ""; - - $nav = elgg_view('navigation/pagination', array( - 'baseurl' => $_SERVER['REQUEST_URI'], - 'offset' => $offset, - 'count' => $count, - 'limit' => $limit, - 'word' => 'annoff', - 'nonefound' => false, - )); - - $html .= $nav; - - if (is_array($annotations) && sizeof($annotations) > 0) { - foreach ($annotations as $annotation) { - $html .= elgg_view_annotation($annotation, "", false); - } - } - - if ($count) { - $html .= $nav; +function elgg_view_annotation_list($annotations, array $vars = array()) { + $defaults = array( + 'items' => $annotations, + 'list_class' => 'elgg-list-annotation elgg-annotation-list', // @todo remove elgg-annotation-list in Elgg 1.9 + 'full_view' => true, + 'offset_key' => 'annoff', + ); + + $vars = array_merge($defaults, $vars); + + if (!$vars["limit"] && !$vars["offset"]) {
+ // no need for pagination if listing is unlimited
+ $vars["pagination"] = false;
} - return $html; + return elgg_view('page/components/list', $vars); } /** @@ -912,27 +1088,23 @@ function elgg_view_annotation_list($annotations, $count, $offset, $limit) { * * This is called automatically by the framework from {@link elgg_view_entity()} * - * @param ElggEntity $entity Entity - * @param bool $full Full view? + * @param ElggEntity $entity Entity + * @param bool $full_view Display full view? * * @return mixed string or false on failure * @todo Change the hook name. */ -function elgg_view_entity_annotations(ElggEntity $entity, $full = true) { - if (!$entity) { - return false; - } - +function elgg_view_entity_annotations(ElggEntity $entity, $full_view = true) { if (!($entity instanceof ElggEntity)) { return false; } $entity_type = $entity->getType(); - $annotations = trigger_plugin_hook('entity:annotate', $entity_type, + $annotations = elgg_trigger_plugin_hook('entity:annotate', $entity_type, array( 'entity' => $entity, - 'full' => $full, + 'full_view' => $full_view, ) ); @@ -940,59 +1112,24 @@ function elgg_view_entity_annotations(ElggEntity $entity, $full = true) { } /** - * Displays a layout with optional parameters. - * - * Layouts control the static elements in Elgg's appearance. - * There are a few default layouts in core: - * - administration A special layout for the admin area. - * - one_column A single column page with a header and footer. - * - one_column_with_sidebar A single column page with a header, footer, and sidebar. - * - widgets A widget canvas. + * Renders a title. * - * Arguments to this function are passed to the layouts as $area1, $area2, - * ... $areaN. See the individual layouts for what options are supported. + * This is a shortcut for {@elgg_view page/elements/title}. * - * Layouts are stored in canvas/layouts/$layout_name. + * @param string $title The page title + * @param array $vars View variables (was submenu be displayed? (deprecated)) * - * @tip When calling this function, be sure to name the variable argument - * names as something meaningful. Avoid the habit of using $areaN as the - * argument names. - * - * @param string $layout The name of the views in canvas/layouts/. - * - * @return string The layout - * @todo Make this consistent with the rest of the view functions by passing - * an array instead of "$areaN". + * @return string The HTML (etc) */ -function elgg_view_layout($layout) { - $arg = 1; - $param_array = array(); - while ($arg < func_num_args()) { - $param_array['area' . $arg] = func_get_arg($arg); - $arg++; - } - - if (elgg_view_exists("canvas/layouts/{$layout}")) { - return elgg_view("canvas/layouts/{$layout}", $param_array); - } else { - return elgg_view("canvas/default", $param_array); +function elgg_view_title($title, $vars = array()) { + if (!is_array($vars)) { + elgg_deprecated_notice('setting $submenu in elgg_view_title() is deprecated', 1.8); + $vars = array('submenu' => $vars); } -} -/** - * Returns a rendered title. - * - * This is a shortcut for {@elgg_view page_elements/title}. - * - * @param string $title The page title - * @param string $submenu Should a submenu be displayed? (default false, use not recommended) - * - * @return string The HTML (etc) - */ -function elgg_view_title($title, $submenu = false) { - $title = elgg_view('page_elements/title', array('title' => $title, 'submenu' => $submenu)); + $vars['title'] = $title; - return $title; + return elgg_view('page/elements/title', $vars); } /** @@ -1015,214 +1152,353 @@ function elgg_view_friendly_time($time) { * * @tip Plugins can override the output by registering a handler * for the comments, $entity_type hook. The handler is responsible - * for formatting the comments and add comment form. + * for formatting the comments and the add comment form. * * @param ElggEntity $entity The entity to view comments of - * @param bool $add_comment Include a form to add comments + * @param bool $add_comment Include a form to add comments? + * @param array $vars Variables to pass to comment view * - * @return string|false The HTML (etc) for the comments, or false on failure + * @return string|false Rendered comments or false on failure * @link http://docs.elgg.org/Entities/Comments * @link http://docs.elgg.org/Annotations/Comments */ -function elgg_view_comments($entity, $add_comment = true) { +function elgg_view_comments($entity, $add_comment = true, array $vars = array()) { if (!($entity instanceof ElggEntity)) { return false; } - $comments = trigger_plugin_hook('comments', $entity->getType(), array('entity' => $entity), false); - if ($comemnts) { - return $comments; - } else { - $comments = list_annotations($entity->getGUID(), 'generic_comment'); - - //display the new comment form if required - if ($add_comment) { - $comments .= elgg_view('comments/forms/edit', array('entity' => $entity)); - } + $vars['entity'] = $entity; + $vars['show_add_form'] = $add_comment; + $vars['class'] = elgg_extract('class', $vars, "{$entity->getSubtype()}-comments"); - return $comments; + $output = elgg_trigger_plugin_hook('comments', $entity->getType(), $vars, false); + if ($output) { + return $output; + } else { + return elgg_view('page/elements/comments', $vars); } } - /** - * Wrapper function to display search listings. + * Wrapper function for the image block display pattern. + * + * Fixed width media on the side (image, icon, flash, etc.). + * Descriptive content filling the rest of the column. + * + * This is a shortcut for {@elgg_view page/components/image_block}. * - * @param string $icon The icon for the listing - * @param string $info Any information that needs to be displayed. + * @param string $image The icon and other information + * @param string $body Description content + * @param array $vars Additional parameters for the view * - * @return string The HTML (etc) representing the listing + * @return string + * @since 1.8.0 */ -function elgg_view_listing($icon, $info) { - return elgg_view('entities/entity_listing', array('icon' => $icon, 'info' => $info)); +function elgg_view_image_block($image, $body, $vars = array()) { + $vars['image'] = $image; + $vars['body'] = $body; + return elgg_view('page/components/image_block', $vars); } /** - * Registers a function to handle templates. + * Wrapper function for the module display pattern. * - * Alternative template handlers can be registered to handle - * all output functions. By default, {@link elgg_view()} will - * simply include the view file. If an alternate template handler - * is registered, the view name and passed $vars will be passed to the - * registered function, which is then responsible for generating and returning - * output. + * Box with header, body, footer * - * Template handlers need to accept two arguments: string $view_name and array - * $vars. + * This is a shortcut for {@elgg_view page/components/module}. * - * @warning This is experimental. + * @param string $type The type of module (main, info, popup, aside, etc.) + * @param string $title A title to put in the header + * @param string $body Content of the module + * @param array $vars Additional parameters for the module * - * @param string $function_name The name of the function to pass to. + * @return string + * @since 1.8.0 + */ +function elgg_view_module($type, $title, $body, array $vars = array()) { + $vars['class'] = elgg_extract('class', $vars, '') . " elgg-module-$type"; + $vars['title'] = $title; + $vars['body'] = $body; + return elgg_view('page/components/module', $vars); +} + +/** + * Renders a human-readable representation of a river item * - * @return bool - * @see elgg_view() - * @link http://docs.elgg.org/Views/TemplateHandlers + * @param ElggRiverItem $item A river item object + * @param array $vars An array of variables for the view + * + * @return string returns empty string if could not be rendered */ -function set_template_handler($function_name) { - global $CONFIG; - if (!empty($function_name) && is_callable($function_name)) { - $CONFIG->template_handler = $function_name; - return true; +function elgg_view_river_item($item, array $vars = array()) { + if (!($item instanceof ElggRiverItem)) { + return ''; } - return false; + // checking default viewtype since some viewtypes do not have unique views per item (rss) + $view = $item->getView(); + if (!$view || !elgg_view_exists($view, 'default')) { + return ''; + } + + $subject = $item->getSubjectEntity(); + $object = $item->getObjectEntity(); + if (!$subject || !$object) { + // subject is disabled or subject/object deleted + return ''; + } + + // @todo this needs to be cleaned up + // Don't hide objects in closed groups that a user can see. + // see https://github.com/elgg/elgg/issues/4789 + // else { + // // hide based on object's container + // $visibility = ElggGroupItemVisibility::factory($object->container_guid); + // if ($visibility->shouldHideItems) { + // return ''; + // } + // } + + $vars['item'] = $item; + + return elgg_view('river/item', $vars); } /** - * Extends a view with another view. + * Convenience function for generating a form from a view in a standard location. * - * The output of any view can be prepended or appended to any other view. + * This function assumes that the body of the form is located at "forms/$action" and + * sets the action by default to "action/$action". Automatically wraps the forms/$action + * view with a <form> tag and inserts the anti-csrf security tokens. * - * The default action is to append a view. If the priority is less than 500, - * the output of the extended view will be appended to the original view. + * @tip This automatically appends elgg-form-action-name to the form's class. It replaces any + * slashes with dashes (blog/save becomes elgg-form-blog-save) * - * Priority can be specified and affects the order in which extensions - * are appended or prepended. + * @example + * <code>echo elgg_view_form('login');</code> * - * @internal View extensions are stored in - * $CONFIG->views->extensions[$view][$priority] = $view_extension + * This would assume a "login" form body to be at "forms/login" and would set the action + * of the form to "http://yoursite.com/action/login". * - * @param string $view The view to extend. - * @param string $view_extension This view is added to $view - * @param int $priority The priority, from 0 to 1000, - * to add at (lowest numbers displayed first) - * @param string $viewtype Not used + * If elgg_view('forms/login') is: + * <input type="text" name="username" /> + * <input type="password" name="password" /> * - * @return void - * @since 1.7.0 - * @link http://docs.elgg.org/Views/Ejxtend - * @example views/extend.php + * Then elgg_view_form('login') generates: + * <form action="http://yoursite.com/action/login" method="post"> + * ...security tokens... + * <input type="text" name="username" /> + * <input type="password" name="password" /> + * </form> + * + * @param string $action The name of the action. An action name does not include + * the leading "action/". For example, "login" is an action name. + * @param array $form_vars $vars environment passed to the "input/form" view + * @param array $body_vars $vars environment passed to the "forms/$action" view + * + * @return string The complete form */ -function elgg_extend_view($view, $view_extension, $priority = 501, $viewtype = '') { +function elgg_view_form($action, $form_vars = array(), $body_vars = array()) { global $CONFIG; - if (!isset($CONFIG->views)) { - $CONFIG->views = new stdClass; - } + $defaults = array( + 'action' => $CONFIG->wwwroot . "action/$action", + 'body' => elgg_view("forms/$action", $body_vars) + ); - if (!isset($CONFIG->views->extensions)) { - $CONFIG->views->extensions = array(); - } + $form_class = 'elgg-form-' . preg_replace('/[^a-z0-9]/i', '-', $action); - if (!isset($CONFIG->views->extensions[$view])) { - $CONFIG->views->extensions[$view][500] = "{$view}"; - } - - while (isset($CONFIG->views->extensions[$view][$priority])) { - $priority++; + // append elgg-form class to any class options set + if (isset($form_vars['class'])) { + $form_vars['class'] = $form_vars['class'] . " $form_class"; + } else { + $form_vars['class'] = $form_class; } - $CONFIG->views->extensions[$view][$priority] = "{$view_extension}"; - ksort($CONFIG->views->extensions[$view]); + return elgg_view('input/form', array_merge($defaults, $form_vars)); } /** - * Unextends a view. + * View an item in a list * - * @param string $view The view that was extended. - * @param string $view_extension This view that was added to $view + * @param ElggEntity|ElggAnnotation $item + * @param array $vars Additional parameters for the rendering * - * @return bool - * @since 1.7.2 + * @return string + * @since 1.8.0 + * @access private */ -function elgg_unextend_view($view, $view_extension) { +function elgg_view_list_item($item, array $vars = array()) { global $CONFIG; - if (!isset($CONFIG->views)) { - return FALSE; + $type = $item->getType(); + if (in_array($type, $CONFIG->entity_types)) { + return elgg_view_entity($item, $vars); + } else if ($type == 'annotation') { + return elgg_view_annotation($item, $vars); + } else if ($type == 'river') { + return elgg_view_river_item($item, $vars); } - if (!isset($CONFIG->views->extensions)) { - return FALSE; - } + return ''; +} - if (!isset($CONFIG->views->extensions[$view])) { - return FALSE; +/** + * View one of the elgg sprite icons + * + * Shorthand for <span class="elgg-icon elgg-icon-$name"></span> + * + * @param string $name The specific icon to display + * @param string $class Additional class: float, float-alt, or custom class + * + * @return string The html for displaying an icon + */ +function elgg_view_icon($name, $class = '') { + // @todo deprecate boolean in Elgg 1.9 + if ($class === true) { + $class = 'float'; } + return "<span class=\"elgg-icon elgg-icon-$name $class\"></span>"; +} - $priority = array_search($view_extension, $CONFIG->views->extensions[$view]); - if ($priority === FALSE) { - return FALSE; +/** + * Displays a user's access collections, using the core/friends/collections view + * + * @param int $owner_guid The GUID of the owning user + * + * @return string A formatted rendition of the collections + * @todo Move to the friends/collection.php page. + * @access private + */ +function elgg_view_access_collections($owner_guid) { + if ($collections = get_user_access_collections($owner_guid)) { + foreach ($collections as $key => $collection) { + $collections[$key]->members = get_members_of_access_collection($collection->id, true); + $collections[$key]->entities = get_user_friends($owner_guid, "", 9999); + } } - unset($CONFIG->views->extensions[$view][$priority]); - - return TRUE; + return elgg_view('core/friends/collections', array('collections' => $collections)); } /** - * Extend a view + * Registers a function to handle templates. * - * @deprecated 1.7. Use elgg_extend_view(). + * Alternative template handlers can be registered to handle + * all output functions. By default, {@link elgg_view()} will + * simply include the view file. If an alternate template handler + * is registered, the view name and passed $vars will be passed to the + * registered function, which is then responsible for generating and returning + * output. * - * @param string $view The view to extend. - * @param string $view_name This view is added to $view - * @param int $priority The priority, from 0 to 1000, - * to add at (lowest numbers displayed first) - * @param string $viewtype Not used + * Template handlers need to accept two arguments: string $view_name and array + * $vars. * - * @return void + * @warning This is experimental. + * + * @param string $function_name The name of the function to pass to. + * + * @return bool + * @see elgg_view() + * @link http://docs.elgg.org/Views/TemplateHandlers */ -function extend_view($view, $view_name, $priority = 501, $viewtype = '') { - elgg_deprecated_notice('extend_view() was deprecated by elgg_extend_view()!', 1.7); - elgg_extend_view($view, $view_name, $priority, $viewtype); +function set_template_handler($function_name) { + global $CONFIG; + + if (is_callable($function_name)) { + $CONFIG->template_handler = $function_name; + return true; + } + return false; } /** - * Set an alternative base location for a view. + * Returns the name of views for in a directory. * - * Views are expected to be in plugin_name/views/. This function can - * be used to change that location. + * Use this to get all namespaced views under the first element. * - * @internal Core view locations are stored in $CONFIG->viewpath. + * @param string $dir The main directory that holds the views. (mod/profile/views/) + * @param string $base The root name of the view to use, without the viewtype. (profile) * - * @tip This is useful to optionally register views in a plugin. + * @return array + * @since 1.7.0 + * @todo Why isn't this used anywhere else but in elgg_view_tree()? + * Seems like a useful function for autodiscovery. + * @access private + */ +function elgg_get_views($dir, $base) { + $return = array(); + if (file_exists($dir) && is_dir($dir)) { + if ($handle = opendir($dir)) { + while ($view = readdir($handle)) { + if (!in_array($view, array('.', '..', '.svn', 'CVS'))) { + if (is_dir($dir . '/' . $view)) { + if ($val = elgg_get_views($dir . '/' . $view, $base . '/' . $view)) { + $return = array_merge($return, $val); + } + } else { + $view = str_replace('.php', '', $view); + $return[] = $base . '/' . $view; + } + } + } + } + } + + return $return; +} + +/** + * Returns all views below a partial view. * - * @param string $view The name of the view - * @param string $location The base location path - * @param string $viewtype The view type + * Settings $view_root = 'profile' will show all available views under + * the "profile" namespace. * - * @return void + * @param string $view_root The root view + * @param string $viewtype Optionally specify a view type + * other than the current one. + * + * @return array A list of view names underneath that root view + * @todo This is used once in the deprecated get_activity_stream_data() function. + * @access private */ -function set_view_location($view, $location, $viewtype = '') { +function elgg_view_tree($view_root, $viewtype = "") { global $CONFIG; + static $treecache = array(); - if (empty($viewtype)) { - $viewtype = 'default'; + // Get viewtype + if (!$viewtype) { + $viewtype = elgg_get_viewtype(); } - if (!isset($CONFIG->views)) { - $CONFIG->views = new stdClass; + // A little light internal caching + if (!empty($treecache[$view_root])) { + return $treecache[$view_root]; } - if (!isset($CONFIG->views->locations)) { - $CONFIG->views->locations = array($viewtype => array($view => $location)); + // Examine $CONFIG->views->locations + if (isset($CONFIG->views->locations[$viewtype])) { + foreach ($CONFIG->views->locations[$viewtype] as $view => $path) { + $pos = strpos($view, $view_root); + if ($pos === 0) { + $treecache[$view_root][] = $view; + } + } + } - } else if (!isset($CONFIG->views->locations[$viewtype])) { - $CONFIG->views->locations[$viewtype] = array($view => $location); + // Now examine core + $location = $CONFIG->viewpath; + $viewtype = elgg_get_viewtype(); + $root = $location . $viewtype . '/' . $view_root; - } else { - $CONFIG->views->locations[$viewtype][$view] = $location; + if (file_exists($root) && is_dir($root)) { + $val = elgg_get_views($root, $view_root); + if (!is_array($treecache[$view_root])) { + $treecache[$view_root] = array(); + } + $treecache[$view_root] = array_merge($treecache[$view_root], $val); } + + return $treecache[$view_root]; } /** @@ -1234,19 +1510,16 @@ function set_view_location($view, $location, $viewtype = '') { * * @param string $view_base Optional The base of the view name without the view type. * @param string $folder Required The folder to begin looking in - * @param string $base_location_path The base views directory to use with set_view_location + * @param string $base_location_path The base views directory to use with elgg_set_view_location() * @param string $viewtype The type of view we're looking at (default, rss, etc) * - * @return void + * @return bool returns false if folder can't be read * @since 1.7.0 - * @see set_view_location() + * @see elgg_set_view_location() * @todo This seems overly complicated. + * @access private */ function autoregister_views($view_base, $folder, $base_location_path, $viewtype) { - if (!isset($i)) { - $i = 0; - } - if ($handle = opendir($folder)) { while ($view = readdir($handle)) { if (!in_array($view, array('.', '..', '.svn', 'CVS')) && !is_dir($folder . "/" . $view)) { @@ -1259,7 +1532,7 @@ function autoregister_views($view_base, $folder, $base_location_path, $viewtype) $view_base_new = ""; } - set_view_location($view_base_new . str_replace('.php', '', $view), + elgg_set_view_location($view_base_new . str_replace('.php', '', $view), $base_location_path, $viewtype); } } else if (!in_array($view, array('.', '..', '.svn', 'CVS')) && is_dir($folder . "/" . $view)) { @@ -1279,73 +1552,44 @@ function autoregister_views($view_base, $folder, $base_location_path, $viewtype) } /** - * Assembles and outputs a full page. - * - * A "page" in Elgg is determined by the current view type and - * can be HTML for a browser, RSS for a feed reader, or - * Javascript, PHP and a number of other formats. - * - * @param string $title Title - * @param string $body Body - * @param string $page_shell Optional page shell to use. - * @param array $vars Optional vars array to pass to the page - * shell. Automatically adds title, body, and sysmessages + * Add the rss link to the extras when if needed * - * @return string The contents of the page - * @since 1.8 + * @return void + * @access private */ -function elgg_view_page($title, $body, $page_shell = 'page_shells/default', $vars = array()) { - // get messages - try for errors first - $sysmessages = system_messages(NULL, "errors"); +function elgg_views_add_rss_link() { + global $autofeed; + if (isset($autofeed) && $autofeed == true) { + $url = current_page_url(); + if (substr_count($url, '?')) { + $url .= "&view=rss"; + } else { + $url .= "?view=rss"; + } - if (count($sysmessages["errors"]) == 0) { - // no errors so grab rest of messages - $sysmessages = system_messages(null, ""); - } else { - // we have errors - clear out remaining messages - system_messages(null, ""); + $url = elgg_format_url($url); + elgg_register_menu_item('extras', array( + 'name' => 'rss', + 'text' => elgg_view_icon('rss'), + 'href' => $url, + 'title' => elgg_echo('feed:rss'), + )); } - - $vars['title'] = $title; - $vars['body'] = $body; - $vars['sysmessages'] = $sysmessages; - - // Draw the page - $output = elgg_view($page_shell, $vars); - - $vars['page_shell'] = $page_shell; - - // Allow plugins to mod output - return trigger_plugin_hook('output', 'page', $vars, $output); } /** - * @deprecated 1.8 Use elgg_view_page() - */ -function page_draw($title, $body, $page_shell = 'page_shells/default', $vars = array()) { - elgg_deprecated_notice("page_draw() was deprecated in favor of elgg_view_page() in 1.8.", 1.8); - echo elgg_view_page($title, $body, $page_shell, $vars); -} - -/** - * Checks if $view_type is valid on this installation. + * Registers deprecated views to avoid making some pages from older plugins + * completely empty. * - * @param string $view_type View type - * - * @return bool - * @since 1.7.2 + * @access private */ -function elgg_is_valid_view_type($view_type) { - global $CONFIG; - - if (!isset($CONFIG->view_types) || !is_array($CONFIG->view_types)) { - return FALSE; +function elgg_views_handle_deprecated_views() { + $location = elgg_get_view_location('page_elements/contentwrapper'); + if ($location === "/var/www/views/") { + elgg_extend_view('page_elements/contentwrapper', 'page/elements/wrapper'); } - - return in_array($view_type, $CONFIG->view_types); } - /** * Initialize viewtypes on system boot event * This ensures simplecache is cleared during upgrades. See #2252 @@ -1357,22 +1601,65 @@ function elgg_is_valid_view_type($view_type) { function elgg_views_boot() { global $CONFIG; - elgg_view_register_simplecache('css'); - elgg_view_register_simplecache('js/friendsPickerv1'); - elgg_view_register_simplecache('js/initialise_elgg'); + elgg_register_simplecache_view('css/ie'); + elgg_register_simplecache_view('css/ie6'); + elgg_register_simplecache_view('css/ie7'); + + elgg_register_js('jquery', '/vendors/jquery/jquery-1.6.4.min.js', 'head'); + elgg_register_js('jquery-ui', '/vendors/jquery/jquery-ui-1.8.16.min.js', 'head'); + elgg_register_js('jquery.form', '/vendors/jquery/jquery.form.js'); + + elgg_register_simplecache_view('js/elgg'); + $elgg_js_url = elgg_get_simplecache_url('js', 'elgg'); + elgg_register_js('elgg', $elgg_js_url, 'head'); + + elgg_load_js('jquery'); + elgg_load_js('jquery-ui'); + elgg_load_js('elgg'); + + elgg_register_simplecache_view('js/lightbox'); + $lightbox_js_url = elgg_get_simplecache_url('js', 'lightbox'); + elgg_register_js('lightbox', $lightbox_js_url); + + elgg_register_simplecache_view('css/lightbox'); + $lightbox_css_url = elgg_get_simplecache_url('css', 'lightbox'); + elgg_register_css('lightbox', $lightbox_css_url); + + elgg_register_simplecache_view('css/elgg'); + $elgg_css_url = elgg_get_simplecache_url('css', 'elgg'); + elgg_register_css('elgg', $elgg_css_url); + + elgg_load_css('elgg'); + + elgg_register_ajax_view('js/languages'); + + elgg_register_plugin_hook_handler('output:before', 'layout', 'elgg_views_add_rss_link'); // discover the built-in view types - // @todo cache this + // @todo the cache is loaded in load_plugins() but we need to know view_types earlier $view_path = $CONFIG->viewpath; - $CONFIG->view_types = array(); $views = scandir($view_path); foreach ($views as $view) { - if ('.' !== substr($view, 0, 1) && is_dir($view_path . $view)) { - $CONFIG->view_types[] = $view; + if ($view[0] !== '.' && is_dir($view_path . $view)) { + elgg_register_viewtype($view); } } + + // set default icon sizes - can be overridden in settings.php or with plugin + if (!isset($CONFIG->icon_sizes)) { + $icon_sizes = array( + 'topbar' => array('w' => 16, 'h' => 16, 'square' => TRUE, 'upscale' => TRUE), + 'tiny' => array('w' => 25, 'h' => 25, 'square' => TRUE, 'upscale' => TRUE), + 'small' => array('w' => 40, 'h' => 40, 'square' => TRUE, 'upscale' => TRUE), + 'medium' => array('w' => 100, 'h' => 100, 'square' => TRUE, 'upscale' => TRUE), + 'large' => array('w' => 200, 'h' => 200, 'square' => FALSE, 'upscale' => FALSE), + 'master' => array('w' => 550, 'h' => 550, 'square' => FALSE, 'upscale' => FALSE), + ); + elgg_set_config('icon_sizes', $icon_sizes); + } } -register_elgg_event_handler('boot', 'system', 'elgg_views_boot', 1000);
\ No newline at end of file +elgg_register_event_handler('boot', 'system', 'elgg_views_boot'); +elgg_register_event_handler('init', 'system', 'elgg_views_handle_deprecated_views'); diff --git a/engine/lib/api.php b/engine/lib/web_services.php index 93de88d5b..51cad6f39 100644 --- a/engine/lib/api.php +++ b/engine/lib/web_services.php @@ -1,7 +1,7 @@ <?php /** - * Elgg API - * Functions and objects that make up the API engine. + * Elgg web services API + * Functions and objects for exposing custom web services. * * @package Elgg.Core * @subpackage WebServicesAPI @@ -29,6 +29,7 @@ * ) * ) */ +global $API_METHODS; $API_METHODS = array(); /** @@ -82,14 +83,14 @@ $call_method = "GET", $require_api_auth = false, $require_user_auth = false) { if ($parameters != NULL) { if (!is_array($parameters)) { - $msg = sprintf(elgg_echo('InvalidParameterException:APIParametersArrayStructure'), $method); + $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method)); throw new InvalidParameterException($msg); } // catch common mistake of not setting up param array correctly $first = current($parameters); if (!is_array($first)) { - $msg = sprintf(elgg_echo('InvalidParameterException:APIParametersArrayStructure'), $method); + $msg = elgg_echo('InvalidParameterException:APIParametersArrayStructure', array($method)); throw new InvalidParameterException($msg); } } @@ -115,8 +116,8 @@ $call_method = "GET", $require_api_auth = false, $require_user_auth = false) { $API_METHODS[$method]["call_method"] = 'GET'; break; default : - $msg = sprintf(elgg_echo('InvalidParameterException:UnrecognisedHttpMethod'), - $call_method, $method); + $msg = elgg_echo('InvalidParameterException:UnrecognisedHttpMethod', + array($call_method, $method)); throw new InvalidParameterException($msg); } @@ -153,34 +154,31 @@ function unexpose_function($method) { * @return true or throws an exception * @throws APIException * @since 1.7.0 + * @access private */ function authenticate_method($method) { global $API_METHODS; // method must be exposed if (!isset($API_METHODS[$method])) { - throw new APIException(sprintf(elgg_echo('APIException:MethodCallNotImplemented'), $method)); - } - - // make sure that POST variables are available if needed - // @todo this may not be needed anymore due to adding %{QUERY_STRING} in .htaccess in 1.7.2 - if (get_call_method() === 'POST' && empty($_POST)) { - include_post_data(); + throw new APIException(elgg_echo('APIException:MethodCallNotImplemented', array($method))); } // check API authentication if required if ($API_METHODS[$method]["require_api_auth"] == true) { - if (pam_authenticate(null, "api") == false) { + $api_pam = new ElggPAM('api'); + if ($api_pam->authenticate() !== true) { throw new APIException(elgg_echo('APIException:APIAuthenticationFailed')); } } - $user_auth_result = pam_authenticate(); + $user_pam = new ElggPAM('user'); + $user_auth_result = $user_pam->authenticate(array()); // check if user authentication is required if ($API_METHODS[$method]["require_user_auth"] == true) { if ($user_auth_result == false) { - throw new APIException(elgg_echo('APIException:UserAuthenticationFailed')); + throw new APIException($user_pam->getFailureMessage(), ErrorResult::$RESULT_FAIL_AUTHTOKEN); } } @@ -195,13 +193,14 @@ function authenticate_method($method) { * * @return GenericResult The result of the execution. * @throws APIException, CallException + * @access private */ function execute_method($method) { global $API_METHODS, $CONFIG; // method must be exposed if (!isset($API_METHODS[$method])) { - $msg = sprintf(elgg_echo('APIException:MethodCallNotImplemented'), $method); + $msg = elgg_echo('APIException:MethodCallNotImplemented', array($method)); throw new APIException($msg); } @@ -209,14 +208,14 @@ function execute_method($method) { if (!(isset($API_METHODS[$method]["function"])) || !(is_callable($API_METHODS[$method]["function"]))) { - $msg = sprintf(elgg_echo('APIException:FunctionDoesNotExist'), $method); + $msg = elgg_echo('APIException:FunctionDoesNotExist', array($method)); throw new APIException($msg); } // check http call method if (strcmp(get_call_method(), $API_METHODS[$method]["call_method"]) != 0) { - $msg = sprintf(elgg_echo('CallException:InvalidCallMethod'), $method, - $API_METHODS[$method]["call_method"]); + $msg = elgg_echo('CallException:InvalidCallMethod', array($method, + $API_METHODS[$method]["call_method"])); throw new CallException($msg); } @@ -233,6 +232,7 @@ function execute_method($method) { $function = $API_METHODS[$method]["function"]; $serialised_parameters = trim($serialised_parameters, ", "); + // @todo document why we cannot use call_user_func_array here $result = eval("return $function($serialised_parameters);"); // Sanity check result @@ -242,13 +242,13 @@ function execute_method($method) { } if ($result === false) { - $msg = sprintf(elgg_echo('APIException:FunctionParseError'), $function, $serialised_parameters); + $msg = elgg_echo('APIException:FunctionParseError', array($function, $serialised_parameters)); throw new APIException($msg); } if ($result === NULL) { // If no value - $msg = sprintf(elgg_echo('APIException:FunctionNoReturn'), $function, $serialised_parameters); + $msg = elgg_echo('APIException:FunctionNoReturn', array($function, $serialised_parameters)); throw new APIException($msg); } @@ -260,6 +260,7 @@ function execute_method($method) { * Get the request method. * * @return string HTTP request method + * @access private */ function get_call_method() { return $_SERVER['REQUEST_METHOD']; @@ -274,6 +275,7 @@ function get_call_method() { * @param string $method The method * * @return array containing parameters as key => value + * @access private */ function get_parameters_for_method($method) { global $API_METHODS; @@ -284,7 +286,7 @@ function get_parameters_for_method($method) { if (isset($API_METHODS[$method]['parameters'])) { foreach ($API_METHODS[$method]['parameters'] as $k => $v) { $param = get_input($k); // Make things go through the sanitiser - if ($param !== '') { + if ($param !== '' && $param !== null) { $sanitised[$k] = $param; } else { // parameter wasn't passed so check for default @@ -303,6 +305,7 @@ function get_parameters_for_method($method) { * Since this is called through a handler, we need to manually get the post data * * @return POST data as string encoded as multipart/form-data + * @access private */ function get_post_data() { @@ -312,37 +315,6 @@ function get_post_data() { } /** - * This fixes the post parameters that are munged due to page handler - * - * @since 1.7.0 - * - * @return void - */ -function include_post_data() { - - $postdata = get_post_data(); - - if (isset($postdata)) { - $query_arr = elgg_parse_str($postdata); - - // grrrr... magic quotes is turned on so we need to strip slashes - if (ini_get_bool('magic_quotes_gpc')) { - if (function_exists('stripslashes_deep')) { - // defined in input.php to handle magic quotes - $query_arr = stripslashes_deep($query_arr); - } - } - - if (is_array($query_arr)) { - foreach ($query_arr as $name => $val) { - set_input($name, $val); - } - } - - } -} - -/** * Verify that the required parameters are present * * @param string $method Method name @@ -351,6 +323,7 @@ function include_post_data() { * @return true on success or exception * @throws APIException * @since 1.7.0 + * @access private */ function verify_parameters($method, $parameters) { global $API_METHODS; @@ -365,13 +338,13 @@ function verify_parameters($method, $parameters) { // this tests the expose structure: must be array to describe parameter and type must be defined if (!is_array($value) || !isset($value['type'])) { - $msg = sprintf(elgg_echo('APIException:InvalidParameter'), $key, $method); + $msg = elgg_echo('APIException:InvalidParameter', array($key, $method)); throw new APIException($msg); } // Check that the variable is present in the request if required if ($value['required'] && !array_key_exists($key, $parameters)) { - $msg = sprintf(elgg_echo('APIException:MissingParameterInMethod'), $key, $method); + $msg = elgg_echo('APIException:MissingParameterInMethod', array($key, $method)); throw new APIException($msg); } } @@ -388,6 +361,7 @@ function verify_parameters($method, $parameters) { * @return string or exception * @throws APIException * @since 1.7.0 + * @access private */ function serialise_parameters($method, $parameters) { global $API_METHODS; @@ -433,7 +407,7 @@ function serialise_parameters($method, $parameters) { case 'array': // we can handle an array of strings, maybe ints, definitely not booleans or other arrays if (!is_array($parameters[$key])) { - $msg = sprintf(elgg_echo('APIException:ParameterNotArray'), $key); + $msg = elgg_echo('APIException:ParameterNotArray', array($key)); throw new APIException($msg); } @@ -454,7 +428,7 @@ function serialise_parameters($method, $parameters) { $serialised_parameters .= $array; break; default: - $msg = sprintf(elgg_echo('APIException:UnrecognisedTypeCast'), $value['type'], $key, $method); + $msg = elgg_echo('APIException:UnrecognisedTypeCast', array($value['type'], $key, $method)); throw new APIException($msg); } } @@ -472,6 +446,7 @@ function serialise_parameters($method, $parameters) { * @return mixed * @throws APIException * @since 1.7.0 + * @access private */ function api_auth_key() { global $CONFIG; @@ -491,7 +466,7 @@ function api_auth_key() { // can be used for keeping stats // plugin can also return false to fail this authentication method - return trigger_plugin_hook('api_key', 'use', $api_key, true); + return elgg_trigger_plugin_hook('api_key', 'use', $api_key, true); } @@ -502,6 +477,7 @@ function api_auth_key() { * * @throws SecurityException * @since 1.7.0 + * @access private */ function api_auth_hmac() { global $CONFIG; @@ -548,8 +524,8 @@ function api_auth_hmac() { $calculated_posthash = calculate_posthash($postdata, $api_header->posthash_algo); if (strcmp($api_header->posthash, $calculated_posthash) != 0) { - $msg = sprintf(elgg_echo('SecurityException:InvalidPostHash'), - $calculated_posthash, $api_header->posthash); + $msg = elgg_echo('SecurityException:InvalidPostHash', + array($calculated_posthash, $api_header->posthash)); throw new SecurityException($msg); } @@ -566,6 +542,7 @@ function api_auth_hmac() { * * @return stdClass Containing all the values. * @throws APIException Detailing any error. + * @access private */ function get_and_validate_api_headers() { $result = new stdClass; @@ -638,6 +615,7 @@ function get_and_validate_api_headers() { * * @return string The php algorithm * @throws APIException if an algorithm is not supported. + * @access private */ function map_api_hash($algo) { $algo = strtolower(sanitise_string($algo)); @@ -652,7 +630,7 @@ function map_api_hash($algo) { return $supported_algos[$algo]; } - throw new APIException(sprintf(elgg_echo('APIException:AlgorithmNotSupported'), $algo)); + throw new APIException(elgg_echo('APIException:AlgorithmNotSupported', array($algo))); } /** @@ -670,6 +648,7 @@ function map_api_hash($algo) { * @param string $post_hash Optional sha1 hash of the post data. * * @return string The HMAC signature + * @access private */ function calculate_hmac($algo, $time, $nonce, $api_key, $secret_key, $get_variables, $post_hash = "") { @@ -700,6 +679,7 @@ $get_variables, $post_hash = "") { * @param string $algo The algorithm used. * * @return string The hash. + * @access private */ function calculate_posthash($postdata, $algo) { $ctx = hash_init(map_api_hash($algo)); @@ -716,6 +696,7 @@ function calculate_posthash($postdata, $algo) { * @param string $hmac The hmac string. * * @return bool True if replay detected, false if not. + * @access private */ function cache_hmac_check_replay($hmac) { // cache lifetime is 25 hours (this should be related to the time drift @@ -813,6 +794,7 @@ function remove_api_user($site_guid, $api_key) { * session code of Elgg, that user will be logged out of all other sessions. * * @return bool + * @access private */ function pam_auth_usertoken() { global $CONFIG; @@ -857,9 +839,10 @@ function pam_auth_usertoken() { * See if the user has a valid login sesson * * @return bool + * @access private */ function pam_auth_session() { - return isloggedin(); + return elgg_is_logged_in(); } // user token functions @@ -1002,6 +985,7 @@ function remove_expired_user_tokens() { * @param array $headers The array of headers "key" => "value" * * @return string + * @access private */ function serialise_api_headers(array $headers) { $headers_str = ""; @@ -1039,7 +1023,7 @@ $content_type = 'application/octet-stream') { case 'POST' : break; default: - $msg = sprintf(elgg_echo('NotImplementedException:CallMethodNotImplemented'), $method); + $msg = elgg_echo('NotImplementedException:CallMethodNotImplemented', array($method)); throw new NotImplementedException($msg); } @@ -1157,6 +1141,7 @@ function get_standard_api_key_array($secret_key, $api_key) { * Simple api to return a list of all api's installed on the system. * * @return array + * @access private */ function list_all_apis() { global $API_METHODS; @@ -1178,9 +1163,21 @@ function list_all_apis() { * * @return string Token string or exception * @throws SecurityException + * @access private */ function auth_gettoken($username, $password) { - if (authenticate($username, $password)) { + // check if username is an email address
+ if (is_email_address($username)) {
+ $users = get_user_by_email($username);
+
+ // check if we have a unique user
+ if (is_array($users) && (count($users) == 1)) {
+ $username = $users[0]->username;
+ }
+ }
+
+ // validate username and password + if (true === elgg_authenticate($username, $password)) { $token = create_user_token($username); if ($token) { return $token; @@ -1208,6 +1205,9 @@ $ERRORS = array(); * @param array $vars Vars * * @return void + * @access private + * + * @throws Exception */ function _php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) { global $ERRORS; @@ -1245,6 +1245,7 @@ function _php_api_error_handler($errno, $errmsg, $filename, $linenum, $vars) { * @param Exception $exception Exception * * @return void + * @access private */ function _php_api_exception_handler($exception) { @@ -1267,6 +1268,7 @@ function _php_api_exception_handler($exception) { * @param array $request Request string * * @return void + * @access private */ function service_handler($handler, $request) { global $CONFIG; @@ -1276,25 +1278,23 @@ function service_handler($handler, $request) { $request = explode('/', $request); // after the handler, the first identifier is response format - // ex) http://example.org/services/api/rest/xml/?method=test - $reponse_format = array_shift($request); + // ex) http://example.org/services/api/rest/json/?method=test + $response_format = array_shift($request); // Which view - xml, json, ... - if ($reponse_format) { - elgg_set_viewtype($reponse_format); + if ($response_format && elgg_is_valid_view_type($response_format)) { + elgg_set_viewtype($response_format); } else { - // default to xml - elgg_set_viewtype("xml"); + // default to json + elgg_set_viewtype("json"); } if (!isset($CONFIG->servicehandler) || empty($handler)) { // no handlers set or bad url header("HTTP/1.0 404 Not Found"); exit; - } else if (isset($CONFIG->servicehandler[$handler]) - && is_callable($CONFIG->servicehandler[$handler])) { - + } else if (isset($CONFIG->servicehandler[$handler]) && is_callable($CONFIG->servicehandler[$handler])) { $function = $CONFIG->servicehandler[$handler]; - $function($request, $handler); + call_user_func($function, $request, $handler); } else { // no handler for this web service header("HTTP/1.0 404 Not Found"); @@ -1313,10 +1313,11 @@ function service_handler($handler, $request) { */ function register_service_handler($handler, $function) { global $CONFIG; + if (!isset($CONFIG->servicehandler)) { $CONFIG->servicehandler = array(); } - if (is_callable($function)) { + if (is_callable($function, true)) { $CONFIG->servicehandler[$handler] = $function; return true; } @@ -1331,42 +1332,89 @@ function register_service_handler($handler, $function) { * * @param string $handler web services type * - * @return 1.7.0 + * @return void + * @since 1.7.0 */ function unregister_service_handler($handler) { global $CONFIG; - if (isset($CONFIG->servicehandler) && isset($CONFIG->servicehandler[$handler])) { + + if (isset($CONFIG->servicehandler, $CONFIG->servicehandler[$handler])) { unset($CONFIG->servicehandler[$handler]); } } -// REST handler - /** * REST API handler * * @return void + * @access private + * + * @throws SecurityException|APIException */ function rest_handler() { global $CONFIG; - require $CONFIG->path . "services/api/rest_api.php"; + // Register the error handler + error_reporting(E_ALL); + set_error_handler('_php_api_error_handler'); + + // Register a default exception handler + set_exception_handler('_php_api_exception_handler'); + + // Check to see if the api is available + if ((isset($CONFIG->disable_api)) && ($CONFIG->disable_api == true)) { + throw new SecurityException(elgg_echo('SecurityException:APIAccessDenied')); + } + + // plugins should return true to control what API and user authentication handlers are registered + if (elgg_trigger_plugin_hook('rest', 'init', null, false) == false) { + // for testing from a web browser, you can use the session PAM + // do not use for production sites!! + //register_pam_handler('pam_auth_session'); + + // user token can also be used for user authentication + register_pam_handler('pam_auth_usertoken'); + + // simple API key check + register_pam_handler('api_auth_key', "sufficient", "api"); + // hmac + register_pam_handler('api_auth_hmac', "sufficient", "api"); + } + + // Get parameter variables + $method = get_input('method'); + $result = null; + + // this will throw an exception if authentication fails + authenticate_method($method); + + $result = execute_method($method); + + + if (!($result instanceof GenericResult)) { + throw new APIException(elgg_echo('APIException:ApiResultUnknown')); + } + + // Output the result + echo elgg_view_page($method, elgg_view("api/output", array("result" => $result))); } -// Initialisation +// Initialization /** * Unit tests for API * - * @param sting $hook unit_test + * @param string $hook unit_test * @param string $type system * @param mixed $value Array of tests * @param mixed $params Params * * @return array + * @access private */ function api_unit_test($hook, $type, $value, $params) { global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/services/api.php'; return $value; } @@ -1375,28 +1423,32 @@ function api_unit_test($hook, $type, $value, $params) { * Initialise the API subsystem. * * @return void + * @access private */ function api_init() { // Register a page handler, so we can have nice URLs register_service_handler('rest', 'rest_handler'); - register_plugin_hook('unit_test', 'system', 'api_unit_test'); + elgg_register_plugin_hook_handler('unit_test', 'system', 'api_unit_test'); // expose the list of api methods expose_function("system.api.list", "list_all_apis", NULL, elgg_echo("system.api.list"), "GET", false, false); // The authentication token api - expose_function("auth.gettoken", - "auth_gettoken", array( - 'username' => array ('type' => 'string'), - 'password' => array ('type' => 'string'), - ), - elgg_echo('auth.gettoken'), - 'POST', - false, - false); + expose_function( + "auth.gettoken", + "auth_gettoken", + array( + 'username' => array ('type' => 'string'), + 'password' => array ('type' => 'string'), + ), + elgg_echo('auth.gettoken'), + 'POST', + false, + false + ); } -register_elgg_event_handler('init', 'system', 'api_init'); +elgg_register_event_handler('init', 'system', 'api_init'); diff --git a/engine/lib/widgets.php b/engine/lib/widgets.php index 02ab3af69..699462a1b 100644 --- a/engine/lib/widgets.php +++ b/engine/lib/widgets.php @@ -8,199 +8,126 @@ */ /** - * Register a particular context for use with widgets. + * Get widgets for a particular context * - * @param string $context The context we wish to enable context for + * The widgets are ordered for display and grouped in columns. + * $widgets = elgg_get_widgets(elgg_get_logged_in_user_guid(), 'dashboard'); + * $first_column_widgets = $widgets[1]; * - * @return void + * @param int $user_guid The owner user GUID + * @param string $context The context (profile, dashboard, etc) + * + * @return array An 2D array of ElggWidget objects + * @since 1.8.0 */ -function use_widgets($context) { - global $CONFIG; - - if (!isset($CONFIG->widgets)) { - $CONFIG->widgets = new stdClass; +function elgg_get_widgets($user_guid, $context) { + $options = array( + 'type' => 'object', + 'subtype' => 'widget', + 'owner_guid' => $user_guid, + 'private_setting_name' => 'context', + 'private_setting_value' => $context, + 'limit' => 0 + ); + $widgets = elgg_get_entities_from_private_settings($options); + if (!$widgets) { + return array(); } - if (!isset($CONFIG->widgets->contexts)) { - $CONFIG->widgets->contexts = array(); + $sorted_widgets = array(); + foreach ($widgets as $widget) { + if (!isset($sorted_widgets[(int)$widget->column])) { + $sorted_widgets[(int)$widget->column] = array(); + } + $sorted_widgets[(int)$widget->column][$widget->order] = $widget; } - if (!empty($context)) { - $CONFIG->widgets->contexts[] = $context; + foreach ($sorted_widgets as $col => $widgets) { + ksort($sorted_widgets[$col]); } -} -/** - * Determines whether or not the current context is using widgets - * - * @return bool Depending on widget status - */ -function using_widgets() { - global $CONFIG; - - $context = elgg_get_context(); - if (isset($CONFIG->widgets->contexts) && is_array($CONFIG->widgets->contexts)) { - if (in_array($context, $CONFIG->widgets->contexts)) { - return true; - } - } - - return false; + return $sorted_widgets; } /** - * When given a widget entity and a new requested location, saves the new location - * and also provides a sensible ordering for all widgets in that column + * Create a new widget instance * - * @param ElggObject $widget The widget entity - * @param int $order The order within the column - * @param int $column The column (1, 2 or 3) + * @param int $owner_guid GUID of entity that owns this widget + * @param string $handler The handler for this widget + * @param string $context The context for this widget + * @param int $access_id If not specified, it is set to the default access level * - * @return bool Depending on success + * @return int|false Widget GUID or false on failure + * @since 1.8.0 */ -function save_widget_location(ElggObject $widget, $order, $column) { - if ($widget instanceof ElggObject) { - if ($widget->subtype == "widget") { - // If you can't move the widget, don't save a new location - if (!$widget->draggable) { - return false; - } - - // Sanitise the column value - if ($column != 1 || $column != 2 || $column != 3) { - $column = 1; - } - - $widget->column = (int) $column; - - $ordertmp = array(); - $params = array( - 'context' => $widget->context, - 'column' => $column, - ); - - if ($entities = get_entities_from_metadata_multi($params, 'object', 'widget')) { - foreach ($entities as $entity) { - $entityorder = $entity->order; - if ($entityorder < $order) { - $ordertmp[$entityorder] = $entity; - } - if ($entityorder >= $order) { - $ordertmp[$entityorder + 10000] = $entity; - } - } - } - - $ordertmp[$order] = $widget; - ksort($ordertmp); - - $orderticker = 10; - foreach ($ordertmp as $orderval => $entity) { - $entity->order = $orderticker; - $orderticker += 10; - } - - return true; - } else { - register_error($widget->subtype); - } - +function elgg_create_widget($owner_guid, $handler, $context, $access_id = null) { + if (empty($owner_guid) || empty($handler) || !elgg_is_widget_type($handler)) { + return false; } - return false; -} - -/** - * Get widgets for a particular context and column, in order of display - * - * @param int $user_guid The owner user GUID - * @param string $context The context (profile, dashboard etc) - * @param int $column The column (1 or 2) - * - * @return array|false An array of widget ElggObjects, or false - */ -function get_widgets($user_guid, $context, $column) { - $params = array( - 'column' => $column, - 'context' => $context - ); - $widgets = get_entities_from_private_setting_multi($params, "object", - "widget", $user_guid, "", 10000); - - if ($widgets) { - $widgetorder = array(); - foreach ($widgets as $widget) { - $order = $widget->order; - while (isset($widgetorder[$order])) { - $order++; - } - $widgetorder[$order] = $widget; - } + $owner = get_entity($owner_guid); + if (!$owner) { + return false; + } - ksort($widgetorder); + $widget = new ElggWidget; + $widget->owner_guid = $owner_guid; + $widget->container_guid = $owner_guid; // @todo - will this work for group widgets + if (isset($access_id)) { + $widget->access_id = $access_id; + } else { + $widget->access_id = get_default_access(); + } - return $widgetorder; + if (!$widget->save()) { + return false; } - return false; -} + // private settings cannot be set until ElggWidget saved + $widget->handler = $handler; + $widget->context = $context; -/** - * Displays a particular widget - * - * @param ElggObject $widget The widget to display - * - * @return string The HTML for the widget, including JavaScript wrapper - */ -function display_widget(ElggObject $widget) { - return elgg_view_entity($widget); + return $widget->getGUID(); } /** - * Add a new widget instance + * Can the user edit the widget layout + * + * Triggers a 'permissions_check', 'widget_layout' plugin hook * - * @param int $entity_guid GUID of entity that owns this widget - * @param string $handler The handler for this widget - * @param string $context The page context for this widget - * @param int $order The order to display this widget in - * @param int $column The column to display this widget in (1, 2 or 3) - * @param int $access_id If not specified, it is set to the default access level + * @param string $context The widget context + * @param int $user_guid The GUID of the user (0 for logged in user) * - * @return bool Depending on success + * @return bool + * @since 1.8.0 */ -function add_widget($entity_guid, $handler, $context, $order = 0, $column = 1, $access_id = null) { - if (empty($entity_guid) || empty($context) || empty($handler) || !widget_type_exists($handler)) { - return false; - } - - if ($entity = get_entity($entity_guid)) { - $widget = new ElggWidget; - $widget->owner_guid = $entity_guid; - $widget->container_guid = $entity_guid; - if (isset($access_id)) { - $widget->access_id = $access_id; - } else { - $widget->access_id = get_default_access(); - } - - if (!$widget->save()) { - return false; - } +function elgg_can_edit_widget_layout($context, $user_guid = 0) { - $widget->handler = $handler; - $widget->context = $context; - $widget->column = $column; - $widget->order = $order; + $user = get_entity((int)$user_guid); + if (!$user) { + $user = elgg_get_logged_in_user_entity(); + } - // save_widget_location($widget, $order, $column); - return true; + $return = false; + if (elgg_is_admin_logged_in()) { + $return = true; + } + if (elgg_get_page_owner_guid() == $user->guid) { + $return = true; } - return false; + $params = array( + 'user' => $user, + 'context' => $context, + 'page_owner' => elgg_get_page_owner_entity() + ); + return elgg_trigger_plugin_hook('permissions_check', 'widget_layout', $params, $return); } /** - * Define a new widget type + * Regsiter a widget type + * + * This should be called by plugins in their init function. * * @param string $handler The identifier for the widget handler * @param string $name The name of the widget type @@ -208,50 +135,46 @@ function add_widget($entity_guid, $handler, $context, $order = 0, $column = 1, $ * @param string $context A comma-separated list of contexts where this * widget is allowed (default: 'all') * @param bool $multiple Whether or not multiple instances of this widget - * are allowed on a single dashboard (default: false) - * @param string $positions A comma-separated list of positions on the page - * (side or main) where this widget is allowed (default: "side,main") + * are allowed in a single layout (default: false) * - * @return bool Depending on success + * @return bool + * @since 1.8.0 */ -function add_widget_type($handler, $name, $description, $context = "all", -$multiple = false, $positions = "side,main") { - - if (!empty($handler) && !empty($name)) { - global $CONFIG; +function elgg_register_widget_type($handler, $name, $description, $context = "all", $multiple = false) { - if (!isset($CONFIG->widgets)) { - $CONFIG->widgets = new stdClass; - } + if (!$handler || !$name) { + return false; + } - if (!isset($CONFIG->widgets->handlers)) { - $CONFIG->widgets->handlers = array(); - } + global $CONFIG; - $handlerobj = new stdClass; - $handlerobj->name = $name; - $handlerobj->description = $description; - $handlerobj->context = explode(",", $context); - $handlerobj->multiple = $multiple; - $handlerobj->positions = explode(",", $positions); + if (!isset($CONFIG->widgets)) { + $CONFIG->widgets = new stdClass; + } + if (!isset($CONFIG->widgets->handlers)) { + $CONFIG->widgets->handlers = array(); + } - $CONFIG->widgets->handlers[$handler] = $handlerobj; + $handlerobj = new stdClass; + $handlerobj->name = $name; + $handlerobj->description = $description; + $handlerobj->context = explode(",", $context); + $handlerobj->multiple = $multiple; - return true; - } + $CONFIG->widgets->handlers[$handler] = $handlerobj; - return false; + return true; } /** * Remove a widget type * - * @param string $handler The identifier for the widget handler + * @param string $handler The identifier for the widget * * @return void - * @since 1.7.1 + * @since 1.8.0 */ -function remove_widget_type($handler) { +function elgg_unregister_widget_type($handler) { global $CONFIG; if (!isset($CONFIG->widgets)) { @@ -268,243 +191,230 @@ function remove_widget_type($handler) { } /** - * Determines whether or not widgets with the specified handler have been defined + * Has a widget type with the specified handler been registered * * @param string $handler The widget handler identifying string * - * @return bool Whether or not those widgets exist + * @return bool Whether or not that widget type exists + * @since 1.8.0 */ -function widget_type_exists($handler) { +function elgg_is_widget_type($handler) { global $CONFIG; - if (!empty($CONFIG->widgets) - && !empty($CONFIG->widgets->handlers) - && is_array($CONFIG->widgets->handlers) - && array_key_exists($handler, $CONFIG->widgets->handlers)) { - return true; + if (!empty($CONFIG->widgets) && + !empty($CONFIG->widgets->handlers) && + is_array($CONFIG->widgets->handlers) && + array_key_exists($handler, $CONFIG->widgets->handlers)) { + + return true; } return false; } /** - * Returns an array of stdClass objects representing the defined widget types + * Get the widget types for a context + * + * The widget types are stdClass objects. * - * @return array A list of types defined (if any) + * @param string $context The widget context or empty string for current context + * @param bool $exact Only return widgets registered for this context (false) + * + * @return array + * @since 1.8.0 */ -function get_widget_types() { +function elgg_get_widget_types($context = "", $exact = false) { global $CONFIG; - if (!empty($CONFIG->widgets) - && !empty($CONFIG->widgets->handlers) - && is_array($CONFIG->widgets->handlers)) { + if (empty($CONFIG->widgets) || + empty($CONFIG->widgets->handlers) || + !is_array($CONFIG->widgets->handlers)) { + // no widgets + return array(); + } + if (!$context) { $context = elgg_get_context(); + } - foreach ($CONFIG->widgets->handlers as $key => $handler) { - if (!in_array('all', $handler->context) && - !in_array($context, $handler->context)) { - unset($CONFIG->widgets->handlers[$key]); + $widgets = array(); + foreach ($CONFIG->widgets->handlers as $key => $handler) { + if ($exact) { + if (in_array($context, $handler->context)) { + $widgets[$key] = $handler; + } + } else { + if (in_array('all', $handler->context) || in_array($context, $handler->context)) { + $widgets[$key] = $handler; } } - - return $CONFIG->widgets->handlers; } - return array(); + return $widgets; } /** - * Saves a widget's settings (by passing an array of - * (name => value) pairs to save_{$handler}_widget) - * - * @param int $widget_guid The GUID of the widget we're saving to - * @param array $params An array of name => value parameters + * Regsiter entity of object, widget as ElggWidget objects * - * @return bool + * @return void + * @access private */ -function save_widget_info($widget_guid, $params) { - if ($widget = get_entity($widget_guid)) { - - $subtype = $widget->getSubtype(); - - if ($subtype != "widget") { - return false; - } - $handler = $widget->handler; - if (empty($handler) || !widget_type_exists($handler)) { - return false; - } - - if (!$widget->canEdit()) { - return false; - } - - // Save the params to the widget - if (is_array($params) && sizeof($params) > 0) { - foreach ($params as $name => $value) { - - if (!empty($name) && !in_array($name, array( - 'guid', 'owner_guid', 'site_guid' - ))) { - if (is_array($value)) { - // @todo Handle arrays securely - $widget->setMetaData($name, $value, "", true); - } else { - $widget->$name = $value; - } - } - } - $widget->save(); - } - - $function = "save_{$handler}_widget"; - if (is_callable($function)) { - return $function($params); - } - - return true; - } +function elgg_widget_run_once() { + add_subtype("object", "widget", "ElggWidget"); +} - return false; +/** + * Function to initialize widgets functionality + * + * @return void + * @access private + */ +function elgg_widgets_init() { + elgg_register_action('widgets/save'); + elgg_register_action('widgets/add'); + elgg_register_action('widgets/move'); + elgg_register_action('widgets/delete'); + elgg_register_action('widgets/upgrade', '', 'admin'); + + run_function_once("elgg_widget_run_once"); } /** - * Reorders the widgets from a widget panel + * Gets a list of events to create default widgets for and + * register menu items for default widgets with the admin section. * - * @param string $panelstring1 String of guids of ElggWidget objects separated by :: - * @param string $panelstring2 String of guids of ElggWidget objects separated by :: - * @param string $panelstring3 String of guids of ElggWidget objects separated by :: - * @param string $context Profile or dashboard - * @param int $owner Owner guid + * A plugin that wants to register a new context for default widgets should + * register for the plugin hook 'get_list', 'default_widgets'. The handler + * can register the new type of default widgets by adding an associate array to + * the return value array like this: + * array( + * 'name' => elgg_echo('profile'), + * 'widget_context' => 'profile', + * 'widget_columns' => 3, + * + * 'event' => 'create', + * 'entity_type' => 'user', + * 'entity_subtype' => ELGG_ENTITIES_ANY_VALUE, + * ); + * + * The first set of keys define information about the new type of default + * widgets and the second set determine what event triggers the creation of the + * new widgets. * * @return void + * @access private */ -function reorder_widgets_from_panel($panelstring1, $panelstring2, $panelstring3, $context, $owner) { - $return = true; - - $mainwidgets = explode('::', $panelstring1); - $sidewidgets = explode('::', $panelstring2); - $rightwidgets = explode('::', $panelstring3); +function elgg_default_widgets_init() { + global $CONFIG; + $default_widgets = elgg_trigger_plugin_hook('get_list', 'default_widgets', null, array()); - $handlers = array(); - $guids = array(); + $CONFIG->default_widget_info = $default_widgets; - if (is_array($mainwidgets) && sizeof($mainwidgets) > 0) { - foreach ($mainwidgets as $widget) { + if ($default_widgets) { + elgg_register_admin_menu_item('configure', 'default_widgets', 'appearance'); - $guid = (int) $widget; + // override permissions for creating widget on logged out / just created entities + elgg_register_plugin_hook_handler('container_permissions_check', 'object', 'elgg_default_widgets_permissions_override'); - if ("{$guid}" == "{$widget}") { - $guids[1][] = $widget; - } else { - $handlers[1][] = $widget; - } + // only register the callback once per event + $events = array(); + foreach ($default_widgets as $info) { + $events[$info['event'] . ',' . $info['entity_type']] = $info; } - } - if (is_array($sidewidgets) && sizeof($sidewidgets) > 0) { - foreach ($sidewidgets as $widget) { - - $guid = (int) $widget; - - if ("{$guid}" == "{$widget}") { - $guids[2][] = $widget; - } else { - $handlers[2][] = $widget; - } - + foreach ($events as $info) { + elgg_register_event_handler($info['event'], $info['entity_type'], 'elgg_create_default_widgets'); } } - if (is_array($rightwidgets) && sizeof($rightwidgets) > 0) { - foreach ($rightwidgets as $widget) { - - $guid = (int) $widget; +} - if ("{$guid}" == "{$widget}") { - $guids[3][] = $widget; - } else { - $handlers[3][] = $widget; - } +/** + * Creates default widgets + * + * This plugin hook handler is registered for events based on what kinds of + * default widgets have been registered. See elgg_default_widgets_init() for + * information on registering new default widget contexts. + * + * @param string $event The event + * @param string $type The type of object + * @param ElggEntity $entity The entity being created + * @return void + * @access private + */ +function elgg_create_default_widgets($event, $type, $entity) { + $default_widget_info = elgg_get_config('default_widget_info'); - } + if (!$default_widget_info || !$entity) { + return; } - // Reorder existing widgets or delete ones that have vanished - foreach (array(1, 2, 3) as $column) { - if ($dbwidgets = get_widgets($owner, $context, $column)) { - - foreach ($dbwidgets as $dbwidget) { - if (in_array($dbwidget->getGUID(), $guids[1]) - || in_array($dbwidget->getGUID(), $guids[2]) || in_array($dbwidget->getGUID(), $guids[3])) { - - if (in_array($dbwidget->getGUID(), $guids[1])) { - $pos = array_search($dbwidget->getGUID(), $guids[1]); - $col = 1; - } else if (in_array($dbwidget->getGUID(), $guids[2])) { - $pos = array_search($dbwidget->getGUID(), $guids[2]); - $col = 2; - } else { - $pos = array_search($dbwidget->getGUID(), $guids[3]); - $col = 3; - } - $pos = ($pos + 1) * 10; - $dbwidget->column = $col; - $dbwidget->order = $pos; - } else { - $dbguid = $dbwidget->getGUID(); - if (!$dbwidget->delete()) { - $return = false; - } else { - // Remove state cookie - setcookie('widget' + $dbguid, null); + $type = $entity->getType(); + $subtype = $entity->getSubtype(); + + // event is already guaranteed by the hook registration. + // need to check subtype and type. + foreach ($default_widget_info as $info) { + if ($info['entity_type'] == $type) { + if ($info['entity_subtype'] == ELGG_ENTITIES_ANY_VALUE || $info['entity_subtype'] == $subtype) { + + // need to be able to access everything + $old_ia = elgg_set_ignore_access(true); + elgg_push_context('create_default_widgets'); + + // pull in by widget context with widget owners as the site + // not using elgg_get_widgets() because it sorts by columns and we don't care right now. + $options = array( + 'type' => 'object', + 'subtype' => 'widget', + 'owner_guid' => elgg_get_site_entity()->guid, + 'private_setting_name' => 'context', + 'private_setting_value' => $info['widget_context'], + 'limit' => 0 + ); + + $widgets = elgg_get_entities_from_private_settings($options); + /* @var ElggWidget[] $widgets */ + + foreach ($widgets as $widget) { + // change the container and owner + $new_widget = clone $widget; + $new_widget->container_guid = $entity->guid; + $new_widget->owner_guid = $entity->guid; + + // pull in settings + $settings = get_all_private_settings($widget->guid); + + foreach ($settings as $name => $value) { + $new_widget->$name = $value; } - } - } - } - // Add new ones - if (sizeof($guids[$column]) > 0) { - foreach ($guids[$column] as $key => $guid) { - if ($guid == 0) { - $pos = ($key + 1) * 10; - $handler = $handlers[$column][$key]; - if (!add_widget($owner, $handler, $context, $pos, $column)) { - $return = false; - } + $new_widget->save(); } + + elgg_set_ignore_access($old_ia); + elgg_pop_context(); } } } - - return $return; -} - -/** - * Regsiter entity of object, widget as ElggWidget objects - * - * @return void - */ -function widget_run_once() { - // Register a class - add_subtype("object", "widget", "ElggWidget"); } /** - * Function to initialise widgets functionality on Elgg init + * Overrides permissions checks when creating widgets for logged out users. * - * @return void + * @param string $hook The permissions hook. + * @param string $type The type of entity being created. + * @param string $return Value + * @param mixed $params Params + * @return true|null + * @access private */ -function widgets_init() { - register_action('widgets/reorder'); - register_action('widgets/save'); - register_action('widgets/add'); +function elgg_default_widgets_permissions_override($hook, $type, $return, $params) { + if ($type == 'object' && $params['subtype'] == 'widget') { + return elgg_in_context('create_default_widgets') ? true : null; + } - // Now run this stuff, but only once - run_function_once("widget_run_once"); + return null; } -// Register event -register_elgg_event_handler('init', 'system', 'widgets_init'); - -// Use widgets on the dashboard -use_widgets('dashboard');
\ No newline at end of file +elgg_register_event_handler('init', 'system', 'elgg_widgets_init'); +// register default widget hooks from plugins +elgg_register_event_handler('ready', 'system', 'elgg_default_widgets_init'); diff --git a/engine/lib/xml-rpc.php b/engine/lib/xml-rpc.php index 251d470a1..bfe1a8645 100644 --- a/engine/lib/xml-rpc.php +++ b/engine/lib/xml-rpc.php @@ -15,6 +15,7 @@ * @param array $parameters An array of params * * @return array + * @access private */ function xmlrpc_parse_params($parameters) { $result = array(); @@ -32,6 +33,7 @@ function xmlrpc_parse_params($parameters) { * @param XMLObject $object And object * * @return mixed + * @access private */ function xmlrpc_scalar_value($object) { if ($object->name == 'param') { @@ -86,6 +88,7 @@ function xmlrpc_scalar_value($object) { // Functions for adding handlers ////////////////////////////////////////////////////////// /** XML-RPC Handlers */ +global $XML_RPC_HANDLERS; $XML_RPC_HANDLERS = array(); /** @@ -109,6 +112,7 @@ function register_xmlrpc_handler($method, $handler) { * @param XMLRPCCall $parameters The call and parameters. * * @return XMLRPCCall + * @access private */ function trigger_xmlrpc_handler(XMLRPCCall $parameters) { global $XML_RPC_HANDLERS; @@ -119,8 +123,8 @@ function trigger_xmlrpc_handler(XMLRPCCall $parameters) { $result = $handler($parameters); if (!($result instanceof XMLRPCResponse)) { - $msg = sprintf(elgg_echo('InvalidParameterException:UnexpectedReturnFormat'), - $parameters->getMethodName()); + $msg = elgg_echo('InvalidParameterException:UnexpectedReturnFormat', + array($parameters->getMethodName())); throw new InvalidParameterException($msg); } @@ -129,8 +133,8 @@ function trigger_xmlrpc_handler(XMLRPCCall $parameters) { } // if no handler then throw exception - $msg = sprintf(elgg_echo('NotImplementedException:XMLRPCMethodNotImplemented'), - $parameters->getMethodName()); + $msg = elgg_echo('NotImplementedException:XMLRPCMethodNotImplemented', + array($parameters->getMethodName())); throw new NotImplementedException($msg); } @@ -147,6 +151,7 @@ function trigger_xmlrpc_handler(XMLRPCCall $parameters) { * @param array $vars Vars * * @return void + * @access private */ function _php_xmlrpc_error_handler($errno, $errmsg, $filename, $linenum, $vars) { $error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file " @@ -176,6 +181,7 @@ function _php_xmlrpc_error_handler($errno, $errmsg, $filename, $linenum, $vars) * @param Exception $exception The exception * * @return void + * @access private */ function _php_xmlrpc_exception_handler($exception) { diff --git a/engine/lib/xml.php b/engine/lib/xml.php index 0d0d83da0..497459d83 100644 --- a/engine/lib/xml.php +++ b/engine/lib/xml.php @@ -101,47 +101,11 @@ function serialise_array_to_xml(array $data, $n = 0) { /** * Parse an XML file into an object. - * Based on code from http://de.php.net/manual/en/function.xml-parse-into-struct.php by - * efredricksen at gmail dot com * * @param string $xml The XML * - * @return object + * @return ElggXMLElement */ function xml_to_object($xml) { - $parser = xml_parser_create(); - - // Parse $xml into a structure - xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); - xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); - xml_parse_into_struct($parser, $xml, $tags); - - xml_parser_free($parser); - - $elements = array(); - $stack = array(); - - foreach ($tags as $tag) { - $index = count($elements); - - if ($tag['type'] == "complete" || $tag['type'] == "open") { - $elements[$index] = new XmlElement; - $elements[$index]->name = $tag['tag']; - $elements[$index]->attributes = $tag['attributes']; - $elements[$index]->content = $tag['value']; - - if ($tag['type'] == "open") { - $elements[$index]->children = array(); - $stack[count($stack)] = &$elements; - $elements = &$elements[$index]->children; - } - } - - if ($tag['type'] == "close") { - $elements = &$stack[count($stack) - 1]; - unset($stack[count($stack) - 1]); - } - } - - return $elements[0]; + return new ElggXMLElement($xml); } diff --git a/engine/schema/mysql.sql b/engine/schema/mysql.sql index 526cc8ce6..4714b71bb 100644 --- a/engine/schema/mysql.sql +++ b/engine/schema/mysql.sql @@ -94,7 +94,7 @@ CREATE TABLE `prefix_api_users` ( /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `prefix_config` ( - `name` varchar(32) NOT NULL, + `name` varchar(255) NOT NULL, `value` text NOT NULL, `site_guid` int(11) NOT NULL, PRIMARY KEY (`name`,`site_guid`) @@ -108,7 +108,7 @@ CREATE TABLE `prefix_config` ( /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `prefix_datalists` ( - `name` varchar(32) NOT NULL, + `name` varchar(255) NOT NULL, `value` text NOT NULL, PRIMARY KEY (`name`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; @@ -361,6 +361,7 @@ CREATE TABLE `prefix_system_log` ( `access_id` int(11) NOT NULL, `enabled` enum('yes','no') NOT NULL DEFAULT 'yes', `time_created` int(11) NOT NULL, + `ip_address` varchar(46) NOT NULL, PRIMARY KEY (`id`), KEY `object_id` (`object_id`), KEY `object_class` (`object_class`), diff --git a/engine/settings.example.php b/engine/settings.example.php index f2772b8e6..3b139d710 100644 --- a/engine/settings.example.php +++ b/engine/settings.example.php @@ -1,6 +1,6 @@ <?php /** - * Defines database creditials. + * Defines database credentials. * * Most of Elgg's configuration is stored in the database. This file contains the * credentials to connect to the database, as well as a few optional configuration @@ -107,7 +107,7 @@ $CONFIG->broken_mta = FALSE; * * Elgg stores each query and its results in a query cache. * On large sites or long-running scripts, this cache can grow to be - * large. To disable query caching, set this to FALSE. + * large. To disable query caching, set this to TRUE. * * @global bool $CONFIG->db_disable_query_cache */ @@ -120,4 +120,4 @@ $CONFIG->db_disable_query_cache = FALSE; * * @global int $CONFIG->min_password_length */ -$CONFIG->min_password_length = 6;
\ No newline at end of file +$CONFIG->min_password_length = 6; diff --git a/engine/start.php b/engine/start.php index 6dd227d42..55b8ffa5b 100644 --- a/engine/start.php +++ b/engine/start.php @@ -1,12 +1,12 @@ <?php /** - * Bootstraps and starts the Elgg engine. + * Bootstraps the Elgg engine. * * This file loads the full Elgg engine, checks the installation - * state, then emits a series of events to finish booting Elgg: + * state, and triggers a series of events to finish booting Elgg: * - {@elgg_event boot system} - * - {@elgg_event plugins_boot system} * - {@elgg_event init system} + * - {@elgg_event ready system} * * If Elgg is fully uninstalled, the browser will be redirected to an * installation page. @@ -36,13 +36,12 @@ $START_MICROTIME = microtime(true); * Configuration values. * * The $CONFIG global contains configuration values required - * for running Elgg as defined in the settings.php file. The following - * array keys are defined by core Elgg: + * for running Elgg as defined in the settings.php file. * - * Plugin authors are encouraged to use get_config() instead of accessing the - * global directly. + * Plugin authors are encouraged to use elgg_get_config() instead of accessing + * the global directly. * - * @see get_config() + * @see elgg_get_config() * @see engine/settings.php * @global stdClass $CONFIG */ @@ -50,95 +49,73 @@ global $CONFIG; if (!isset($CONFIG)) { $CONFIG = new stdClass; } +$CONFIG->boot_complete = false; $lib_dir = dirname(__FILE__) . '/lib/'; -/** - * The minimum required libs to bootstrap an Elgg installation. - * - * @var array - */ -$required_files = array( - 'elgglib.php', 'views.php', 'access.php', 'system_log.php', 'export.php', - 'sessions.php', 'languages.php', 'input.php', 'install.php', 'cache.php', - 'output.php' -); - -// include bootstraping libs -foreach ($required_files as $file) { - $path = $lib_dir . $file; - if (!include($path)) { - echo "Could not load file '$path'. " - . 'Please check your Elgg installation for all required files.'; - exit; - } +// Load the bootstrapping library +$path = $lib_dir . 'elgglib.php'; +if (!include_once($path)) { + echo "Could not load file '$path'. Please check your Elgg installation for all required files."; + exit; } -// Register the error handler -set_error_handler('_elgg_php_error_handler'); -set_exception_handler('_elgg_php_exception_handler'); - -/** - * Load the system settings - */ +// Load the system settings if (!include_once(dirname(__FILE__) . "/settings.php")) { - $msg = elgg_echo('InstallationException:CannotLoadSettings'); + $msg = 'Elgg could not load the settings file. It does not exist or there is a file permissions issue.'; throw new InstallationException($msg); } // load the rest of the library files from engine/lib/ $lib_files = array( - // these need to be loaded first. - 'database.php', 'actions.php', - - 'admin.php', 'annotations.php', 'api.php', 'calendar.php', - 'configuration.php', 'cron.php', 'entities.php', 'export.php', - 'extender.php', 'filestore.php', 'group.php', - 'location.php', 'mb_wrapper.php', 'memcache.php', 'metadata.php', - 'metastrings.php', 'navigation.php', 'notification.php', 'objects.php', - 'opendd.php', 'pagehandler.php', 'pageowner.php', 'pam.php', 'plugins.php', - 'relationships.php', 'river.php', 'sites.php', 'statistics.php', 'tags.php', - 'usersettings.php', 'users.php', 'version.php', 'widgets.php', 'xml.php', - 'xml-rpc.php' + 'access.php', 'actions.php', 'admin.php', 'annotations.php', 'cache.php', + 'calendar.php', 'configuration.php', 'cron.php', 'database.php', + 'entities.php', 'export.php', 'extender.php', 'filestore.php', 'group.php', + 'input.php', 'languages.php', 'location.php', 'mb_wrapper.php', + 'memcache.php', 'metadata.php', 'metastrings.php', 'navigation.php', + 'notification.php', 'objects.php', 'opendd.php', 'output.php', + 'pagehandler.php', 'pageowner.php', 'pam.php', 'plugins.php', + 'private_settings.php', 'relationships.php', 'river.php', 'sessions.php', + 'sites.php', 'statistics.php', 'system_log.php', 'tags.php', + 'user_settings.php', 'users.php', 'upgrade.php', 'views.php', + 'web_services.php', 'widgets.php', 'xml.php', 'xml-rpc.php', + + // backward compatibility + 'deprecated-1.7.php', 'deprecated-1.8.php', ); foreach ($lib_files as $file) { $file = $lib_dir . $file; elgg_log("Loading $file..."); if (!include_once($file)) { - $msg = sprint(elgg_echo('InstallationException:MissingLibrary'), $file); + $msg = "Could not load $file"; throw new InstallationException($msg); } } -// confirm that the installation completed successfully -verify_installation(); - -// Autodetect some default configuration settings -set_default_config(); - -// Trigger boot events for core. Plugins can't hook -// into this because they haven't been loaded yet. -trigger_elgg_event('boot', 'system'); +// Connect to database, load language files, load configuration, init session +// Plugins can't use this event because they haven't been loaded yet. +elgg_trigger_event('boot', 'system'); // Load the plugins that are active -load_plugins(); -trigger_elgg_event('plugins_boot', 'system'); - -// Trigger system init event for plugins -trigger_elgg_event('init', 'system'); - -// Regenerate the simple cache if expired. -// Don't do it on upgrade because upgrade does it itself. -// @todo - move into function and perhaps run off init system event -if (!defined('UPGRADING')) { - $view = get_input('view', 'default'); - $lastupdate = datalist_get("simplecache_lastupdate_$view"); - $lastcached = datalist_get("simplecache_lastcached_$view"); - if ($lastupdate == 0 || $lastcached < $lastupdate) { - elgg_view_regenerate_simplecache($view); - } - // needs to be set for links in html head - $CONFIG->lastcache = $lastcached; +elgg_load_plugins(); + +// @todo move loading plugins into a single boot function that replaces 'boot', 'system' event +// and then move this code in there. +// This validates the view type - first opportunity to do it is after plugins load. +$view_type = elgg_get_viewtype(); +if (!elgg_is_valid_view_type($view_type)) { + elgg_set_viewtype('default'); } + +// @todo deprecate as plugins can use 'init', 'system' event +elgg_trigger_event('plugins_boot', 'system'); + +// Complete the boot process for both engine and plugins +elgg_trigger_event('init', 'system'); + +$CONFIG->boot_complete = true; + +// System loaded and ready +elgg_trigger_event('ready', 'system'); diff --git a/engine/tests/api/access_collections.php b/engine/tests/api/access_collections.php new file mode 100644 index 000000000..4acfae596 --- /dev/null +++ b/engine/tests/api/access_collections.php @@ -0,0 +1,290 @@ +<?php +/** + * Access Collections tests + * + * @package Elgg + * @subpackage Test + */ +class ElggCoreAccessCollectionsTest extends ElggCoreUnitTest { + + /** + * Called before each test object. + */ + public function __construct() { + parent::__construct(); + + $this->dbPrefix = get_config("dbprefix"); + + $user = new ElggUser(); + $user->username = 'test_user_' . rand(); + $user->email = 'fake_email@fake.com' . rand(); + $user->name = 'fake user'; + $user->access_id = ACCESS_PUBLIC; + $user->salt = generate_random_cleartext_password(); + $user->password = generate_user_password($user, rand()); + $user->owner_guid = 0; + $user->container_guid = 0; + $user->save(); + + $this->user = $user; + } + + /** + * Called before each test method. + */ + public function setUp() { + + } + + /** + * Called after each test method. + */ + public function tearDown() { + // do not allow SimpleTest to interpret Elgg notices as exceptions + $this->swallowErrors(); + } + + /** + * Called after each test object. + */ + public function __destruct() { + // all __destruct() code should go above here + $this->user->delete(); + parent::__destruct(); + } + + public function testCreateGetDeleteACL() { + + $acl_name = 'test access collection'; + $acl_id = create_access_collection($acl_name); + + $this->assertTrue(is_int($acl_id)); + + $q = "SELECT * FROM {$this->dbPrefix}access_collections WHERE id = $acl_id"; + $acl = get_data_row($q); + + $this->assertEqual($acl->id, $acl_id); + + if ($acl) { + $this->assertEqual($acl->name, $acl_name); + + $result = delete_access_collection($acl_id); + $this->assertTrue($result); + + $q = "SELECT * FROM {$this->dbPrefix}access_collections WHERE id = $acl_id"; + $data = get_data($q); + $this->assertIdentical(array(), $data); + } + } + + public function testAddRemoveUserToACL() { + $acl_id = create_access_collection('test acl'); + + $result = add_user_to_access_collection($this->user->guid, $acl_id); + $this->assertTrue($result); + + if ($result) { + $result = remove_user_from_access_collection($this->user->guid, $acl_id); + $this->assertIdentical(true, $result); + } + + delete_access_collection($acl_id); + } + + public function testUpdateACL() { + // another fake user to test with + $user = new ElggUser(); + $user->username = 'test_user_' . rand(); + $user->email = 'fake_email@fake.com' . rand(); + $user->name = 'fake user'; + $user->access_id = ACCESS_PUBLIC; + $user->salt = generate_random_cleartext_password(); + $user->password = generate_user_password($user, rand()); + $user->owner_guid = 0; + $user->container_guid = 0; + $user->save(); + + $acl_id = create_access_collection('test acl'); + + $member_lists = array( + // adding + array( + $this->user->guid, + $user->guid + ), + // removing one, keeping one. + array( + $user->guid + ), + // removing one, adding one + array( + $this->user->guid, + ), + // removing all. + array() + ); + + foreach ($member_lists as $members) { + $result = update_access_collection($acl_id, $members); + $this->assertTrue($result); + + if ($result) { + $q = "SELECT * FROM {$this->dbPrefix}access_collection_membership + WHERE access_collection_id = $acl_id"; + $data = get_data($q); + + if (count($members) == 0) { + $this->assertFalse($data); + } else { + $this->assertEqual(count($members), count($data)); + } + foreach ($data as $row) { + $this->assertTrue(in_array($row->user_guid, $members)); + } + } + } + + delete_access_collection($acl_id); + $user->delete(); + } + + public function testCanEditACL() { + $acl_id = create_access_collection('test acl', $this->user->guid); + + // should be true since it's the owner + $result = can_edit_access_collection($acl_id, $this->user->guid); + $this->assertTrue($result); + + // should be true since IA is on. + $ia = elgg_set_ignore_access(true); + $result = can_edit_access_collection($acl_id); + $this->assertTrue($result); + elgg_set_ignore_access($ia); + + // should be false since IA is off + $ia = elgg_set_ignore_access(false); + $result = can_edit_access_collection($acl_id); + $this->assertFalse($result); + elgg_set_ignore_access($ia); + + delete_access_collection($acl_id); + } + + public function testCanEditACLHook() { + // if only we supported closures! + global $acl_test_info; + + $acl_id = create_access_collection('test acl'); + + $acl_test_info = array( + 'acl_id' => $acl_id, + 'user' => $this->user + ); + + function test_acl_access_hook($hook, $type, $value, $params) { + global $acl_test_info; + if ($params['user_id'] == $acl_test_info['user']->guid) { + $acl = get_access_collection($acl_test_info['acl_id']); + $value[$acl->id] = $acl->name; + } + + return $value; + } + + elgg_register_plugin_hook_handler('access:collections:write', 'all', 'test_acl_access_hook'); + + // enable security since we usually run as admin + $ia = elgg_set_ignore_access(false); + $result = can_edit_access_collection($acl_id, $this->user->guid); + $this->assertTrue($result); + $ia = elgg_set_ignore_access($ia); + + elgg_unregister_plugin_hook_handler('access:collections:write', 'all', 'test_acl_access_hook'); + + delete_access_collection($acl_id); + } + + // groups interface + // only runs if the groups plugin is enabled because implementation is split between + // core and the plugin. + public function testCreateDeleteGroupACL() { + if (!elgg_is_active_plugin('groups')) { + return; + } + + $group = new ElggGroup(); + $group->name = 'Test group'; + $group->save(); + $acl = get_access_collection($group->group_acl); + + // ACLs are owned by groups + $this->assertEqual($acl->owner_guid, $group->guid); + + // removing group and acl + $this->assertTrue($group->delete()); + + $acl = get_access_collection($group->group_acl); + $this->assertFalse($acl); + + $group->delete(); + } + + public function testJoinLeaveGroupACL() { + if (!elgg_is_active_plugin('groups')) { + return; + } + + $group = new ElggGroup(); + $group->name = 'Test group'; + $group->save(); + + $result = $group->join($this->user); + $this->assertTrue($result); + + // disable security since we run as admin + $ia = elgg_set_ignore_access(false); + + // need to set the page owner to emulate being in a group context. + // this is kinda hacky. + elgg_set_page_owner_guid($group->getGUID()); + + if ($result) { + $can_edit = can_edit_access_collection($group->group_acl, $this->user->guid); + $this->assertTrue($can_edit); + } + + $result = $group->leave($this->user); + $this->assertTrue($result); + + if ($result) { + $can_edit = can_edit_access_collection($group->group_acl, $this->user->guid); + $this->assertFalse($can_edit); + } + + elgg_set_ignore_access($ia); + + $group->delete(); + } + + public function testAccessCaching() { + // create a new user to check against + $user = new ElggUser(); + $user->username = 'access_test_user'; + $user->save(); + + foreach (array('get_access_list', 'get_access_array') as $func) { + $cache = _elgg_get_access_cache(); + $cache->clear(); + + // admin users run tests, so disable access + elgg_set_ignore_access(true); + $access = $func($user->getGUID()); + + elgg_set_ignore_access(false); + $access2 = $func($user->getGUID()); + $this->assertNotEqual($access, $access2, "Access test for $func"); + } + + $user->delete(); + } +} diff --git a/engine/tests/api/annotations.php b/engine/tests/api/annotations.php new file mode 100644 index 000000000..c0b0687cc --- /dev/null +++ b/engine/tests/api/annotations.php @@ -0,0 +1,150 @@ +<?php +/** + * Elgg Test annotation api + * + * @package Elgg + * @subpackage Test + */ +class ElggCoreAnnotationAPITest extends ElggCoreUnitTest { + protected $metastrings; + + /** + * Called before each test method. + */ + public function setUp() { + $this->object = new ElggObject(); + } + + /** + * Called after each test method. + */ + public function tearDown() { + // do not allow SimpleTest to interpret Elgg notices as exceptions + $this->swallowErrors(); + + unset($this->object); + } + + public function testElggGetAnnotationsCount() { + $this->object->title = 'Annotation Unit Test'; + $this->object->save(); + + $guid = $this->object->getGUID(); + create_annotation($guid, 'tested', 'tested1', 'text', 0, ACCESS_PUBLIC); + create_annotation($guid, 'tested', 'tested2', 'text', 0, ACCESS_PUBLIC); + + $count = (int)elgg_get_annotations(array( + 'annotation_names' => array('tested'), + 'guid' => $guid, + 'count' => true, + )); + + $this->assertIdentical($count, 2); + + $this->object->delete(); + } + + public function testElggDeleteAnnotations() { + $e = new ElggObject(); + $e->save(); + + for ($i=0; $i<30; $i++) { + $e->annotate('test_annotation', rand(0,10000)); + } + + $options = array( + 'guid' => $e->getGUID(), + 'limit' => 0 + ); + + $annotations = elgg_get_annotations($options); + $this->assertIdentical(30, count($annotations)); + + $this->assertTrue(elgg_delete_annotations($options)); + + $annotations = elgg_get_annotations($options); + $this->assertTrue(empty($annotations)); + + // nothing to delete so null returned + $this->assertNull(elgg_delete_annotations($options)); + + $this->assertTrue($e->delete()); + } + + public function testElggDisableAnnotations() { + $e = new ElggObject(); + $e->save(); + + for ($i=0; $i<30; $i++) { + $e->annotate('test_annotation', rand(0,10000)); + } + + $options = array( + 'guid' => $e->getGUID(), + 'limit' => 0 + ); + + $this->assertTrue(elgg_disable_annotations($options)); + + $annotations = elgg_get_annotations($options); + $this->assertTrue(empty($annotations)); + + access_show_hidden_entities(true); + $annotations = elgg_get_annotations($options); + $this->assertIdentical(30, count($annotations)); + access_show_hidden_entities(false); + + $this->assertTrue($e->delete()); + } + + public function testElggEnableAnnotations() { + $e = new ElggObject(); + $e->save(); + + for ($i=0; $i<30; $i++) { + $e->annotate('test_annotation', rand(0,10000)); + } + + $options = array( + 'guid' => $e->getGUID(), + 'limit' => 0 + ); + + $this->assertTrue(elgg_disable_annotations($options)); + + // cannot see any annotations so returns null + $this->assertNull(elgg_enable_annotations($options)); + + access_show_hidden_entities(true); + $this->assertTrue(elgg_enable_annotations($options)); + access_show_hidden_entities(false); + + $annotations = elgg_get_annotations($options); + $this->assertIdentical(30, count($annotations)); + + $this->assertTrue($e->delete()); + } + + public function testElggAnnotationExists() { + $e = new ElggObject(); + $e->save(); + $guid = $e->getGUID(); + + $this->assertFalse(elgg_annotation_exists($guid, 'test_annotation')); + + $e->annotate('test_annotation', rand(0, 10000)); + $this->assertTrue(elgg_annotation_exists($guid, 'test_annotation')); + // this metastring should always exist but an annotation of this name should not + $this->assertFalse(elgg_annotation_exists($guid, 'email')); + + $options = array( + 'guid' => $guid, + 'limit' => 0 + ); + $this->assertTrue(elgg_disable_annotations($options)); + $this->assertTrue(elgg_annotation_exists($guid, 'test_annotation')); + + $this->assertTrue($e->delete()); + $this->assertFalse(elgg_annotation_exists($guid, 'test_annotation')); + } +} diff --git a/engine/tests/api/entity_getter_functions.php b/engine/tests/api/entity_getter_functions.php index ce14bd4ee..fef9dc0c5 100644 --- a/engine/tests/api/entity_getter_functions.php +++ b/engine/tests/api/entity_getter_functions.php @@ -10,7 +10,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { * Called before each test object. */ public function __construct() { - elgg_set_ignore_access(TRUE); + elgg_set_ignore_access(TRUE); $this->entities = array(); $this->subtypes = array( 'object' => array(), @@ -175,9 +175,10 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { } /** + * Get a mix of valid and invalid types * - * @param unknown_type $num - * @return unknown_type + * @param int $num + * @return array */ public function getRandomMixedTypes($num = 2) { $have_valid = $have_invalid = false; @@ -196,8 +197,8 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { * Get random mix of valid and invalid subtypes for types given. * * @param array $types - * @param unknown_type $num - * @return unknown_type + * @param int $num + * @return array */ public function getRandomMixedSubtypes(array $types, $num = 2) { $types_c = count($types); @@ -227,6 +228,24 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { return $r; } + /** + * Creates random annotations on $entity + * + * @param ElggEntity $entity + * @param int $max + */ + public function createRandomAnnotations($entity, $max = 1) { + $annotations = array(); + for ($i=0; $i<$max; $i++) { + $name = 'test_annotation_name_' . rand(); + $value = rand(); + $id = create_annotation($entity->getGUID(), $name, $value, 'integer', $entity->getGUID()); + $annotations[] = elgg_get_annotation_from_id($id); + } + + return $annotations; + } + /*********************************** * TYPE TESTS @@ -545,7 +564,9 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { * TYPE_SUBTYPE_PAIRS ***************************/ - + /** + * Valid type, valid subtype pairs + */ public function testElggAPIGettersTSPValidTypeValidSubtype() { $type_num = 1; $subtype_num = 1; @@ -568,6 +589,9 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { } } + /** + * Valid type, multiple valid subtypes + */ public function testElggAPIGettersTSPValidTypeValidPluralSubtype() { $type_num = 1; $subtype_num = 3; @@ -590,6 +614,9 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { } } + /** + * Valid type, both valid and invalid subtypes + */ public function testElggAPIGettersTSPValidTypeMixedPluralSubtype() { $type_num = 1; $valid_subtype_num = 2; @@ -617,9 +644,6 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { } - - - /**************************** * FALSE-RETURNING TESTS **************************** @@ -634,8 +658,8 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { */ - /* - * Test invalid types. + /** + * Test invalid types with singular 'type'. */ public function testElggApiGettersInvalidTypeUsingType() { $type_arr = $this->getRandomInvalids(); @@ -649,7 +673,9 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $this->assertFalse($es); } - + /** + * Test invalid types with plural 'types'. + */ public function testElggApiGettersInvalidTypeUsingTypesAsString() { $type_arr = $this->getRandomInvalids(); $type = $type_arr[0]; @@ -662,8 +688,11 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $this->assertFalse($es); } + /** + * Test invalid types with plural 'types' and an array of a single type + */ public function testElggApiGettersInvalidTypeUsingTypesAsArray() { - $type_arr = $this->getRandomInvalids(); + $type_arr = $this->getRandomInvalids(1); $options = array( 'types' => $type_arr @@ -673,6 +702,9 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $this->assertFalse($es); } + /** + * Test invalid types with plural 'types' and an array of a two types + */ public function testElggApiGettersInvalidTypes() { $type_arr = $this->getRandomInvalids(2); @@ -837,7 +869,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { public function testElggApiGettersEntityNoSubtype() { // create an entity we can later delete. - // order by time created and limit by 1 should == this entity. + // order by guid and limit by 1 should == this entity. $e = new ElggObject(); $e->save(); @@ -1035,7 +1067,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $entities = elgg_get_entities_from_metadata($options); - $this->assertFalse($entities); + $this->assertIdentical(array(), $entities); $e->delete(); } @@ -1063,7 +1095,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $entities = elgg_get_entities_from_metadata($options); - $this->assertFalse($entities); + $this->assertIdentical(array(), $entities); $e->delete(); } @@ -1196,7 +1228,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { } } - function testElggApiGettersEntityMetadatavalueInvalidSingle() { + function testElggApiGettersEntityMetadataValueInvalidSingle() { $subtypes = $this->getRandomValidSubtypes(array('object'), 1); $subtype = $subtypes[0]; $md_name = 'test_metadata_name_' . rand(); @@ -1217,7 +1249,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $entities = elgg_get_entities_from_metadata($options); - $this->assertFalse($entities); + $this->assertIdentical(array(), $entities); $e->delete(); } @@ -1245,7 +1277,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $entities = elgg_get_entities_from_metadata($options); - $this->assertFalse($entities); + $this->assertIdentical(array(), $entities); $e->delete(); } @@ -1323,7 +1355,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $invalid_md_name = 'test_metadata_name_' . rand(); $e = new ElggObject(); $e->subtype = $subtype; - $e->$md_name = $invalid_md_value; + $e->$invalid_md_name = $md_value; $e->save(); $guids[] = $e->getGUID(); @@ -1388,11 +1420,13 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { // make some bad ones $invalid_md_name = 'test_metadata_name_' . rand(); + $invalid_md_name2 = 'test_metadata_name_' . rand(); + $invalid_md_name3 = 'test_metadata_name_' . rand(); $e = new ElggObject(); $e->subtype = $subtype; - $e->$md_name = $invalid_md_value; - $e->$md_name2 = $invalid_md_value; - $e->$md_name3 = $invalid_md_value; + $e->$invalid_md_name = $md_value; + $e->$invalid_md_name2 = $md_value2; + $e->$invalid_md_name3 = $md_value3; $e->save(); $guids[] = $e->getGUID(); @@ -1465,10 +1499,11 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { // make some bad ones $invalid_md_name = 'test_metadata_name_' . rand(); + $invalid_md_name2 = 'test_metadata_name_' . rand(); $e = new ElggObject(); $e->subtype = $subtype; - $e->$md_name = $invalid_md_value; - $e->$md_name2 = $invalid_md_value; + $e->$invalid_md_name = $md_value; + $e->$invalid_md_name2 = $md_value2; $e->save(); $guids[] = $e->getGUID(); @@ -1515,7 +1550,8 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { } } - function testElggApiGettersEntityMetadataNVPValidNValidVEqualsStupid() { + // this keeps locking up my database... + function xtestElggApiGettersEntityMetadataNVPValidNValidVEqualsStupid() { $subtypes = $this->getRandomValidSubtypes(array('object'), 1); $subtype = $subtypes[0]; $md_name = 'test_metadata_name_' . rand(); @@ -1553,11 +1589,11 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $invalid_md_name = 'test_metadata_name_' . rand(); $e = new ElggObject(); $e->subtype = $subtype; - $e->$md_name = $invalid_md_value; - $e->$md_name2 = $invalid_md_value; - $e->$md_name3 = $invalid_md_value; - $e->$md_name4 = $invalid_md_value; - $e->$md_name5 = $invalid_md_value; + $e->$invalid_md_name = $md_value; + $e->$md_name2 = $md_value2; + $e->$md_name3 = $md_value3; + $e->$md_name4 = $md_value4; + $e->$md_name5 = $md_value5; $e->save(); $guids[] = $e->getGUID(); @@ -1619,6 +1655,9 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { } } + /** + * Name value pair with valid name and invalid value + */ function testElggApiGettersEntityMetadataNVPValidNInvalidV() { $subtypes = $this->getRandomValidSubtypes(array('object'), 1); $subtype = $subtypes[0]; @@ -1630,7 +1669,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $invalid_md_name = 'test_metadata_name_' . rand(); $e = new ElggObject(); $e->subtype = $subtype; - $e->$md_name = $invalid_md_value; + $e->$invalid_md_name = $md_value; $e->save(); $guids[] = $e->getGUID(); @@ -1654,7 +1693,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $entities = elgg_get_entities_from_metadata($options); - $this->assertFalse($entities); + $this->assertIdentical(array(), $entities); foreach ($guids as $guid) { if ($e = get_entity($guid)) { @@ -1663,7 +1702,9 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { } } - + /** + * Name value pair with invalid name and valid value + */ function testElggApiGettersEntityMetadataNVPInvalidNValidV() { $subtypes = $this->getRandomValidSubtypes(array('object'), 1); $subtype = $subtypes[0]; @@ -1675,7 +1716,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $invalid_md_name = 'test_metadata_name_' . rand(); $e = new ElggObject(); $e->subtype = $subtype; - $e->$md_name = $invalid_md_value; + $e->$invalid_md_name = $md_value; $e->save(); $guids[] = $e->getGUID(); @@ -1699,7 +1740,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $entities = elgg_get_entities_from_metadata($options); - $this->assertFalse($entities); + $this->assertIdentical(array(), $entities); foreach ($guids as $guid) { if ($e = get_entity($guid)) { @@ -1739,7 +1780,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $invalid_md_name = 'test_metadata_name_' . rand(); $e = new ElggObject(); $e->subtype = $subtype; - $e->$md_name = $invalid_md_value; + $e->$invalid_md_name = $md_value; $e->save(); $guids[] = $e->getGUID(); @@ -1817,7 +1858,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $invalid_md_name = 'test_metadata_name_' . rand(); $e = new ElggObject(); $e->subtype = $subtype; - $e->$md_name = $invalid_md_value; + $e->$invalid_md_name = $md_value; $e->save(); $guids[] = $e->getGUID(); @@ -1869,33 +1910,32 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $subtypes = $this->getRandomValidSubtypes(array('object'), 1); $subtype = $subtypes[0]; $md_name = 'test_metadata_name_' . rand(); - $md_value = 2; $guids = array(); $valid_guids = array(); // our targets $valid = new ElggObject(); $valid->subtype = $subtype; - $valid->$md_name = $md_value; + $valid->$md_name = 1; $valid->save(); $guids[] = $valid->getGUID(); $valid_guids[] = $valid->getGUID(); $valid2 = new ElggObject(); $valid2->subtype = $subtype; - $valid2->$md_name = 3; + $valid2->$md_name = 2; $valid2->save(); $guids[] = $valid->getGUID(); $valid_guids[] = $valid2->getGUID(); $valid3 = new ElggObject(); $valid3->subtype = $subtype; - $valid3->$md_name = 1; + $valid3->$md_name = 3; $valid3->save(); $guids[] = $valid->getGUID(); $valid_guids[] = $valid3->getGUID(); - $md_valid_values = array($md_value, $md_value2); + $md_valid_values = array(1, 2, 3); $options = array( 'type' => 'object', @@ -1928,33 +1968,32 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $subtypes = $this->getRandomValidSubtypes(array('object'), 1); $subtype = $subtypes[0]; $md_name = 'test_metadata_name_' . rand(); - $md_value = 'b'; $guids = array(); $valid_guids = array(); // our targets $valid = new ElggObject(); $valid->subtype = $subtype; - $valid->$md_name = $md_value; + $valid->$md_name = 'a'; $valid->save(); $guids[] = $valid->getGUID(); $valid_guids[] = $valid->getGUID(); $valid2 = new ElggObject(); $valid2->subtype = $subtype; - $valid2->$md_name = 'c'; + $valid2->$md_name = 'b'; $valid2->save(); $guids[] = $valid->getGUID(); $valid_guids[] = $valid2->getGUID(); $valid3 = new ElggObject(); $valid3->subtype = $subtype; - $valid3->$md_name = 'a'; + $valid3->$md_name = 'c'; $valid3->save(); $guids[] = $valid->getGUID(); $valid_guids[] = $valid3->getGUID(); - $md_valid_values = array($md_value, $md_value2); + $md_valid_values = array('a', 'b', 'c'); $options = array( 'type' => 'object', @@ -2039,11 +2078,11 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { } } } - + // Make sure metadata doesn't affect getting entities by relationship. See #2274 public function testElggApiGettersEntityRelationshipWithMetadata() { $guids = array(); - + $obj1 = new ElggObject(); $obj1->test_md = 'test'; $obj1->save(); @@ -2055,29 +2094,29 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $guids[] = $obj2->guid; add_entity_relationship($guids[0], 'test', $guids[1]); - + $options = array( 'relationship' => 'test', 'relationship_guid' => $guids[0] ); - + $es = elgg_get_entities_from_relationship($options); $this->assertTrue(is_array($es)); - $this->assertTrue(count($es), 1); - + $this->assertIdentical(count($es), 1); + foreach ($es as $e) { $this->assertEqual($guids[1], $e->guid); } - + foreach ($guids as $guid) { $e = get_entity($guid); $e->delete(); } } - + public function testElggApiGettersEntityRelationshipWithOutMetadata() { $guids = array(); - + $obj1 = new ElggObject(); $obj1->save(); $guids[] = $obj1->guid; @@ -2087,29 +2126,29 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $guids[] = $obj2->guid; add_entity_relationship($guids[0], 'test', $guids[1]); - + $options = array( 'relationship' => 'test', 'relationship_guid' => $guids[0] ); - + $es = elgg_get_entities_from_relationship($options); $this->assertTrue(is_array($es)); - $this->assertTrue(count($es), 1); - + $this->assertIdentical(count($es), 1); + foreach ($es as $e) { $this->assertEqual($guids[1], $e->guid); } - + foreach ($guids as $guid) { $e = get_entity($guid); $e->delete(); } } - + public function testElggApiGettersEntityRelationshipWithMetadataIncludingRealMetadata() { $guids = array(); - + $obj1 = new ElggObject(); $obj1->test_md = 'test'; $obj1->save(); @@ -2121,31 +2160,31 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $guids[] = $obj2->guid; add_entity_relationship($guids[0], 'test', $guids[1]); - + $options = array( 'relationship' => 'test', 'relationship_guid' => $guids[0], 'metadata_name' => 'test_md', 'metadata_value' => 'test', ); - + $es = elgg_get_entities_from_relationship($options); $this->assertTrue(is_array($es)); - $this->assertTrue(count($es), 1); - + $this->assertIdentical(count($es), 1); + foreach ($es as $e) { $this->assertEqual($guids[1], $e->guid); } - + foreach ($guids as $guid) { $e = get_entity($guid); $e->delete(); } } - + public function testElggApiGettersEntityRelationshipWithMetadataIncludingFakeMetadata() { $guids = array(); - + $obj1 = new ElggObject(); $obj1->test_md = 'test'; $obj1->save(); @@ -2157,14 +2196,14 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $guids[] = $obj2->guid; add_entity_relationship($guids[0], 'test', $guids[1]); - + $options = array( 'relationship' => 'test', 'relationship_guid' => $guids[0], 'metadata_name' => 'test_md', 'metadata_value' => 'invalid', ); - + $es = elgg_get_entities_from_relationship($options); $this->assertTrue(empty($es)); @@ -2174,12 +2213,12 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $e->delete(); } } - + public function testElggApiGettersEntitySiteSingular() { global $CONFIG; - + $guids = array(); - + $obj1 = new ElggObject(); $obj1->test_md = 'test'; // luckily this is never checked. @@ -2193,28 +2232,28 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $obj2->site_guid = $CONFIG->site->guid; $obj2->save(); $guids[] = $obj2->guid; - + $options = array( 'metadata_name' => 'test_md', 'metadata_value' => 'test', 'site_guid' => 2 ); - + $es = elgg_get_entities_from_metadata($options); $this->assertTrue(is_array($es)); $this->assertEqual(1, count($es)); $this->assertEqual($right_guid, $es[0]->guid); - + foreach ($guids as $guid) { get_entity($guid)->delete(); } } - + public function testElggApiGettersEntitySiteSingularAny() { global $CONFIG; - + $guids = array(); - + $obj1 = new ElggObject(); $obj1->test_md = 'test'; // luckily this is never checked. @@ -2227,7 +2266,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $obj2->site_guid = $CONFIG->site->guid; $obj2->save(); $guids[] = $obj2->guid; - + $options = array( 'metadata_name' => 'test_md', 'metadata_value' => 'test', @@ -2235,25 +2274,25 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { 'limit' => 2, 'order_by' => 'e.guid DESC' ); - + $es = elgg_get_entities_from_metadata($options); $this->assertTrue(is_array($es)); $this->assertEqual(2, count($es)); - + foreach ($es as $e) { $this->assertTrue(in_array($e->guid, $guids)); } - + foreach ($guids as $guid) { get_entity($guid)->delete(); } } - + public function testElggApiGettersEntitySitePlural() { global $CONFIG; - + $guids = array(); - + $obj1 = new ElggObject(); $obj1->test_md = 'test'; // luckily this is never checked. @@ -2266,7 +2305,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $obj2->site_guid = $CONFIG->site->guid; $obj2->save(); $guids[] = $obj2->guid; - + $options = array( 'metadata_name' => 'test_md', 'metadata_value' => 'test', @@ -2274,25 +2313,25 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { 'limit' => 2, 'order_by' => 'e.guid DESC' ); - + $es = elgg_get_entities_from_metadata($options); $this->assertTrue(is_array($es)); $this->assertEqual(2, count($es)); - + foreach ($es as $e) { $this->assertTrue(in_array($e->guid, $guids)); } - + foreach ($guids as $guid) { get_entity($guid)->delete(); } } - + public function testElggApiGettersEntitySitePluralSomeInvalid() { global $CONFIG; - + $guids = array(); - + $obj1 = new ElggObject(); $obj1->test_md = 'test'; // luckily this is never checked. @@ -2305,7 +2344,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $obj2->save(); $guids[] = $obj2->guid; $right_guid = $obj2->guid; - + $options = array( 'metadata_name' => 'test_md', 'metadata_value' => 'test', @@ -2314,23 +2353,23 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { 'limit' => 2, 'order_by' => 'e.guid DESC' ); - + $es = elgg_get_entities_from_metadata($options); - + $this->assertTrue(is_array($es)); $this->assertEqual(1, count($es)); $this->assertEqual($es[0]->guid, $right_guid); - + foreach ($guids as $guid) { get_entity($guid)->delete(); } } - + public function testElggApiGettersEntitySitePluralAllInvalid() { global $CONFIG; - + $guids = array(); - + $obj1 = new ElggObject(); $obj1->test_md = 'test'; // luckily this is never checked. @@ -2343,7 +2382,7 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { $obj2->save(); $guids[] = $obj2->guid; $right_guid = $obj2->guid; - + $options = array( 'metadata_name' => 'test_md', 'metadata_value' => 'test', @@ -2352,13 +2391,494 @@ class ElggCoreEntityGetterFunctionsTest extends ElggCoreUnitTest { 'limit' => 2, 'order_by' => 'e.guid DESC' ); - + $es = elgg_get_entities_from_metadata($options); - + $this->assertTrue(empty($es)); - + foreach ($guids as $guid) { get_entity($guid)->delete(); } } + + /** + * Private settings + */ + public function testElggApiGettersEntitiesFromPrivateSettings() { + + // create some test private settings + $setting_name = 'test_setting_name_' . rand(); + $setting_value = rand(1000, 9999); + $setting_name2 = 'test_setting_name_' . rand(); + $setting_value2 = rand(1000, 9999); + + $subtypes = $this->getRandomValidSubtypes(array('object'), 1); + $subtype = $subtypes[0]; + $guids = array(); + + // our targets + $valid = new ElggObject(); + $valid->subtype = $subtype; + $valid->save(); + $guids[] = $valid->getGUID(); + set_private_setting($valid->getGUID(), $setting_name, $setting_value); + set_private_setting($valid->getGUID(), $setting_name2, $setting_value2); + + $valid2 = new ElggObject(); + $valid2->subtype = $subtype; + $valid2->save(); + $guids[] = $valid2->getGUID(); + set_private_setting($valid2->getGUID(), $setting_name, $setting_value); + set_private_setting($valid2->getGUID(), $setting_name2, $setting_value2); + + // simple test with name + $options = array( + 'private_setting_name' => $setting_name + ); + + $entities = elgg_get_entities_from_private_settings($options); + + foreach ($entities as $entity) { + $this->assertTrue(in_array($entity->getGUID(), $guids)); + $value = get_private_setting($entity->getGUID(), $setting_name); + $this->assertEqual($value, $setting_value); + } + + // simple test with value + $options = array( + 'private_setting_value' => $setting_value + ); + + $entities = elgg_get_entities_from_private_settings($options); + + foreach ($entities as $entity) { + $this->assertTrue(in_array($entity->getGUID(), $guids)); + $value = get_private_setting($entity->getGUID(), $setting_name); + $this->assertEqual($value, $setting_value); + } + + // test pairs + $options = array( + 'type' => 'object', + 'subtype' => $subtype, + 'private_setting_name_value_pairs' => array( + array( + 'name' => $setting_name, + 'value' => $setting_value + ), + array( + 'name' => $setting_name2, + 'value' => $setting_value2 + ) + ) + ); + + $entities = elgg_get_entities_from_private_settings($options); + $this->assertEqual(2, count($entities)); + foreach ($entities as $entity) { + $this->assertTrue(in_array($entity->getGUID(), $guids)); + } + + foreach ($guids as $guid) { + if ($e = get_entity($guid)) { + $e->delete(); + } + } + } + + /** + * Location + */ + public function testElggApiGettersEntitiesFromLocation() { + + // a test location that is out of this world + $lat = 500; + $long = 500; + $delta = 5; + + $subtypes = $this->getRandomValidSubtypes(array('object'), 1); + $subtype = $subtypes[0]; + $guids = array(); + + // our objects + $valid = new ElggObject(); + $valid->subtype = $subtype; + $valid->save(); + $guids[] = $valid->getGUID(); + $valid->setLatLong($lat, $long); + + $valid2 = new ElggObject(); + $valid2->subtype = $subtype; + $valid2->save(); + $guids[] = $valid2->getGUID(); + $valid2->setLatLong($lat + 2 * $delta, $long + 2 * $delta); + + // limit to first object + $options = array( + 'latitude' => $lat, + 'longitude' => $long, + 'distance' => $delta + ); + + //global $CONFIG; + //$CONFIG->debug = 'NOTICE'; + $entities = elgg_get_entities_from_location($options); + //unset($CONFIG->debug); + + $this->assertEqual(1, count($entities)); + $this->assertEqual($entities[0]->getGUID(), $valid->getGUID()); + + // get both objects + $options = array( + 'latitude' => $lat, + 'longitude' => $long, + 'distance' => array('latitude' => 2 * $delta, 'longitude' => 2 * $delta) + ); + + $entities = elgg_get_entities_from_location($options); + + $this->assertEqual(2, count($entities)); + foreach ($entities as $entity) { + $this->assertTrue(in_array($entity->getGUID(), $guids)); + } + + foreach ($guids as $guid) { + if ($e = get_entity($guid)) { + $e->delete(); + } + } + } + + + public function testElggGetEntitiesFromRelationshipCount() { + $entities = $this->entities; + $relationships = array(); + $count = count($entities); + $max = $count - 1; + $relationship_name = 'test_relationship_' . rand(0, 1000); + + for ($i = 0; $i < $count; $i++) { + do { + $popular_entity = $entities[array_rand($entities)]; + } while (array_key_exists($popular_entity->guid, $relationships)); + + $relationships[$popular_entity->guid] = array(); + + for ($c = 0; $c < $max; $c++) { + do { + $fan_entity = $entities[array_rand($entities)]; + } while ($fan_entity->guid == $popular_entity->guid || in_array($fan_entity->guid, $relationships[$popular_entity->guid])); + + $relationships[$popular_entity->guid][] = $fan_entity->guid; + add_entity_relationship($fan_entity->guid, $relationship_name, $popular_entity->guid); + } + + $max--; + } + + $options = array( + 'relationship' => $relationship_name, + 'limit' => $count + ); + + $entities = elgg_get_entities_from_relationship_count($options); + + foreach ($entities as $e) { + $options = array( + 'relationship' => $relationship_name, + 'limit' => 100, + 'relationship_guid' => $e->guid, + 'inverse_relationship' => true + ); + + $fan_entities = elgg_get_entities_from_relationship($options); + + $this->assertEqual(count($fan_entities), count($relationships[$e->guid])); + + foreach ($fan_entities as $fan_entity) { + $this->assertTrue(in_array($fan_entity->guid, $relationships[$e->guid])); + $this->assertNotIdentical(false, check_entity_relationship($fan_entity->guid, $relationship_name, $e->guid)); + } + } + } + + public function testElggGetEntitiesByGuidSingular() { + foreach ($this->entities as $e) { + $options = array( + 'guid' => $e->guid + ); + $es = elgg_get_entities($options); + + $this->assertEqual(count($es), 1); + $this->assertEqual($es[0]->guid, $e->guid); + } + } + + public function testElggGetEntitiesByGuidPlural() { + $guids = array(); + + foreach ($this->entities as $e) { + $guids[] = $e->guid; + } + + $options = array( + 'guids' => $guids, + 'limit' => 100 + ); + + $es = elgg_get_entities($options); + + $this->assertEqual(count($es), count($this->entities)); + + foreach ($es as $e) { + $this->assertTrue(in_array($e->guid, $guids)); + } + } + + public function testElggGetEntitiesFromAnnotationsCalculateX() { + $types = array( + 'sum', + 'avg', + 'min', + 'max' + ); + + foreach ($types as $type) { + $subtypes = $this->getRandomValidSubtypes(array('object'), 5); + $name = 'test_annotation_' . rand(0, 9999); + $values = array(); + $options = array( + 'type' => 'object', + 'subtypes' => $subtypes, + 'limit' => 5 + ); + + $es = elgg_get_entities($options); + + foreach ($es as $e) { + $value = rand(0,9999); + $e->annotate($name, $value); + + $value2 = rand(0,9999); + $e->annotate($name, $value2); + + switch ($type) { + case 'sum': + $calc_value = $value + $value2; + break; + + case 'avg': + $calc_value = ($value + $value2) / 2; + break; + + case 'min': + $calc_value = min(array($value, $value2)); + break; + + case 'max': + $calc_value = max(array($value, $value2)); + break; + } + + $values[$e->guid] = $calc_value; + } + + arsort($values); + $order = array_keys($values); + + $options = array( + 'type' => 'object', + 'subtypes' => $subtypes, + 'limit' => 5, + 'annotation_name' => $name, + 'calculation' => $type + ); + + $es = elgg_get_entities_from_annotation_calculation($options); + + foreach ($es as $i => $e) { + $value = 0; + $as = $e->getAnnotations($name); + // should only ever be 2 + $this->assertEqual(2, count($as)); + + $value = $as[0]->value; + $value2 = $as[1]->value; + + switch ($type) { + case 'sum': + $calc_value = $value + $value2; + break; + + case 'avg': + $calc_value = ($value + $value2) / 2; + break; + + case 'min': + $calc_value = min(array($value, $value2)); + break; + + case 'max': + $calc_value = max(array($value, $value2)); + break; + } + + $this->assertEqual($e->guid, $order[$i]); + $this->assertEqual($values[$e->guid], $calc_value); + } + } + } + + public function testElggGetEntitiesFromAnnotationCalculationCount() { + // add two annotations with a unique name to an entity + // then count the number of entities with that annotation name + + $subtypes = $this->getRandomValidSubtypes(array('object'), 1); + $name = 'test_annotation_' . rand(0, 9999); + $values = array(); + $options = array( + 'type' => 'object', + 'subtypes' => $subtypes, + 'limit' => 1 + ); + $es = elgg_get_entities($options); + $entity = $es[0]; + $value = rand(0, 9999); + $entity->annotate($name, $value); + $value = rand(0, 9999); + $entity->annotate($name, $value); + + $options = array( + 'type' => 'object', + 'subtypes' => $subtypes, + 'annotation_name' => $name, + 'calculation' => 'count', + 'count' => true, + ); + $count = elgg_get_entities_from_annotation_calculation($options); + $this->assertEqual(1, $count); + } + + public function testElggGetAnnotationsAnnotationNames() { + $options = array('annotation_names' => array()); + $a_e_map = array(); + + // create test annotations on a few entities. + for ($i=0; $i<3; $i++) { + do { + $e = $this->entities[array_rand($this->entities)]; + } while(in_array($e->guid, $a_e_map)); + $annotations = $this->createRandomAnnotations($e); + + foreach($annotations as $a) { + $options['annotation_names'][] = $a->name; + $a_e_map[$a->id] = $e->guid; + } + } + + $as = elgg_get_annotations($options); + + $this->assertEqual(count($a_e_map), count($as)); + + foreach ($as as $a) { + $this->assertEqual($a_e_map[$a->id], $a->entity_guid); + } + } + + public function testElggGetAnnotationsAnnotationValues() { + $options = array('annotation_values' => array()); + $a_e_map = array(); + + // create test annotations on a few entities. + for ($i=0; $i<3; $i++) { + do { + $e = $this->entities[array_rand($this->entities)]; + } while(in_array($e->guid, $a_e_map)); + $annotations = $this->createRandomAnnotations($e); + + foreach($annotations as $a) { + $options['annotation_values'][] = $a->value; + $a_e_map[$a->id] = $e->guid; + } + } + + $as = elgg_get_annotations($options); + + $this->assertEqual(count($a_e_map), count($as)); + + foreach ($as as $a) { + $this->assertEqual($a_e_map[$a->id], $a->entity_guid); + } + } + + public function testElggGetAnnotationsAnnotationOwnerGuids() { + $options = array('annotation_owner_guids' => array()); + $a_e_map = array(); + + // create test annotations on a single entity + for ($i=0; $i<3; $i++) { + do { + $e = $this->entities[array_rand($this->entities)]; + } while(in_array($e->guid, $a_e_map)); + + // remove annotations left over from previous tests. + elgg_delete_annotations(array('annotation_owner_guid' => $e->guid)); + $annotations = $this->createRandomAnnotations($e); + + foreach($annotations as $a) { + $options['annotation_owner_guids'][] = $e->guid; + $a_e_map[$a->id] = $e->guid; + } + } + + $as = elgg_get_annotations($options); + $this->assertEqual(count($a_e_map), count($as)); + + foreach ($as as $a) { + $this->assertEqual($a_e_map[$a->id], $a->owner_guid); + } + } + + public function testElggGetEntitiesBadWheres() { + $options = array( + 'container_guid' => 'abc' + ); + + $entities = elgg_get_entities($options); + $this->assertFalse($entities); + } + + public function testEGEEmptySubtypePlurality() { + $options = array( + 'type' => 'user', + 'subtypes' => '' + ); + + $entities = elgg_get_entities($options); + $this->assertTrue(is_array($entities)); + + $options = array( + 'type' => 'user', + 'subtype' => '' + ); + + $entities = elgg_get_entities($options); + $this->assertTrue(is_array($entities)); + + $options = array( + 'type' => 'user', + 'subtype' => array('') + ); + + $entities = elgg_get_entities($options); + $this->assertTrue(is_array($entities)); + + $options = array( + 'type' => 'user', + 'subtypes' => array('') + ); + + $entities = elgg_get_entities($options); + $this->assertTrue(is_array($entities)); + } } diff --git a/engine/tests/api/helpers.php b/engine/tests/api/helpers.php index b8c7f048d..414fb4145 100644 --- a/engine/tests/api/helpers.php +++ b/engine/tests/api/helpers.php @@ -31,6 +31,7 @@ class ElggCoreHelpersTest extends ElggCoreUnitTest { global $CONFIG; unset($CONFIG->externals); + unset($CONFIG->externals_map); } /** @@ -62,26 +63,71 @@ class ElggCoreHelpersTest extends ElggCoreUnitTest { $this->assertFalse(elgg_instanceof($bad_entity)); $this->assertFalse(elgg_instanceof($bad_entity, 'object')); $this->assertFalse(elgg_instanceof($bad_entity, 'object', 'test_subtype')); + + remove_subtype('object', 'test_subtype'); + } + + /** + * Test elgg_normalize_url() + */ + public function testElggNormalizeURL() { + $conversions = array( + 'http://example.com' => 'http://example.com', + 'https://example.com' => 'https://example.com', + 'http://example-time.com' => 'http://example-time.com', + + '//example.com' => '//example.com', + 'ftp://example.com/file' => 'ftp://example.com/file', + 'mailto:brett@elgg.org' => 'mailto:brett@elgg.org', + 'javascript:alert("test")' => 'javascript:alert("test")', + 'app://endpoint' => 'app://endpoint', + + 'example.com' => 'http://example.com', + 'example.com/subpage' => 'http://example.com/subpage', + + 'page/handler' => elgg_get_site_url() . 'page/handler', + 'page/handler?p=v&p2=v2' => elgg_get_site_url() . 'page/handler?p=v&p2=v2', + 'mod/plugin/file.php' => elgg_get_site_url() . 'mod/plugin/file.php', + 'mod/plugin/file.php?p=v&p2=v2' => elgg_get_site_url() . 'mod/plugin/file.php?p=v&p2=v2', + 'rootfile.php' => elgg_get_site_url() . 'rootfile.php', + 'rootfile.php?p=v&p2=v2' => elgg_get_site_url() . 'rootfile.php?p=v&p2=v2', + + '/page/handler' => elgg_get_site_url() . 'page/handler', + '/page/handler?p=v&p2=v2' => elgg_get_site_url() . 'page/handler?p=v&p2=v2', + '/mod/plugin/file.php' => elgg_get_site_url() . 'mod/plugin/file.php', + '/mod/plugin/file.php?p=v&p2=v2' => elgg_get_site_url() . 'mod/plugin/file.php?p=v&p2=v2', + '/rootfile.php' => elgg_get_site_url() . 'rootfile.php', + '/rootfile.php?p=v&p2=v2' => elgg_get_site_url() . 'rootfile.php?p=v&p2=v2', + ); + + foreach ($conversions as $input => $output) { + $this->assertIdentical($output, elgg_normalize_url($input)); + } } + /** * Test elgg_register_js() */ public function testElggRegisterJS() { global $CONFIG; - // specify id - $result = elgg_register_js('//test1.com', 'key', 'footer'); + // specify name + $result = elgg_register_js('key', 'http://test1.com', 'footer'); $this->assertTrue($result); - $this->assertIdentical('//test1.com', $CONFIG->externals['javascript']['footer']['key']); + $this->assertTrue(isset($CONFIG->externals_map['js']['key'])); - // let Elgg pick id - $result = elgg_register_js('//test2.com'); - $this->assertTrue($result); - $this->assertIdentical('//test2.com', $CONFIG->externals['javascript']['head'][0]); + $item = $CONFIG->externals_map['js']['key']; + $this->assertTrue($CONFIG->externals['js']->contains($item)); + + $priority = $CONFIG->externals['js']->getPriority($item); + $this->assertTrue($priority !== false); + + $item = $CONFIG->externals['js']->getElement($priority); + $this->assertIdentical('http://test1.com', $item->url); // send a bad url - $result = elgg_register_js(); + $result = elgg_register_js('bad', null); $this->assertFalse($result); } @@ -90,20 +136,20 @@ class ElggCoreHelpersTest extends ElggCoreUnitTest { */ public function testElggRegisterCSS() { global $CONFIG; - - // specify id - $result = elgg_register_css('//test1.com', 'key'); + + // specify name + $result = elgg_register_css('key', 'http://test1.com'); $this->assertTrue($result); - $this->assertIdentical('//test1.com', $CONFIG->externals['css']['head']['key']); + $this->assertTrue(isset($CONFIG->externals_map['css']['key'])); - // let Elgg pick id - $result = elgg_register_css('//test2.com'); - $this->assertTrue($result); - $this->assertIdentical('//test2.com', $CONFIG->externals['css']['head'][1]); - - // send a bad url - $result = elgg_register_js(); - $this->assertFalse($result); + $item = $CONFIG->externals_map['css']['key']; + $this->assertTrue($CONFIG->externals['css']->contains($item)); + + $priority = $CONFIG->externals['css']->getPriority($item); + $this->assertTrue($priority !== false); + + $item = $CONFIG->externals['css']->getElement($priority); + $this->assertIdentical('http://test1.com', $item->url); } /** @@ -112,44 +158,548 @@ class ElggCoreHelpersTest extends ElggCoreUnitTest { public function testElggUnregisterJS() { global $CONFIG; - $urls = array('id1' => '//url1.com', 'id2' => '//url2.com', 'id3' => '//url3.com'); + $base = trim(elgg_get_site_url(), "/"); + + $urls = array('id1' => "$base/urla", 'id2' => "$base/urlb", 'id3' => "$base/urlc"); + foreach ($urls as $id => $url) { - elgg_register_js($url, $id); + elgg_register_js($id, $url); } $result = elgg_unregister_js('id1'); $this->assertTrue($result); - $this->assertNULL($CONFIG->externals['javascript']['head']['id1']); - $result = elgg_unregister_js('', '//url2.com'); - $this->assertTrue($result); - $this->assertNULL($CONFIG->externals['javascript']['head']['id2']); + $js = $CONFIG->externals['js']; + $elements = $js->getElements(); + $this->assertFalse(isset($CONFIG->externals_map['js']['id1'])); + + foreach ($elements as $element) { + if (isset($element->name)) { + $this->assertFalse($element->name == 'id1'); + } + } $result = elgg_unregister_js('id1'); $this->assertFalse($result); - $result = elgg_unregister_js('', '//url2.com'); + + $result = elgg_unregister_js('', 'does_not_exist'); $this->assertFalse($result); - $this->assertIdentical($urls['id3'], $CONFIG->externals['javascript']['head']['id3']); + $result = elgg_unregister_js('id2'); + $elements = $js->getElements(); + + $this->assertFalse(isset($CONFIG->externals_map['js']['id2'])); + foreach ($elements as $element) { + if (isset($element->name)) { + $this->assertFalse($element->name == 'id2'); + } + } + + $this->assertTrue(isset($CONFIG->externals_map['js']['id3'])); + + $priority = $CONFIG->externals['js']->getPriority($CONFIG->externals_map['js']['id3']); + $this->assertTrue($priority !== false); + + $item = $CONFIG->externals['js']->getElement($priority); + $this->assertIdentical($urls['id3'], $item->url); + } + + /** + * Test elgg_load_js() + */ + public function testElggLoadJS() { + global $CONFIG; + + // load before register + elgg_load_js('key'); + $result = elgg_register_js('key', 'http://test1.com', 'footer'); + $this->assertTrue($result); + + $js_urls = elgg_get_loaded_js('footer'); + $this->assertIdentical(array(500 => 'http://test1.com'), $js_urls); } /** - * Test elgg_get_js() + * Test elgg_get_loaded_js() */ public function testElggGetJS() { global $CONFIG; - $urls = array('id1' => '//url1.com', 'id2' => '//url2.com', 'id3' => '//url3.com'); + $base = trim(elgg_get_site_url(), "/"); + + $urls = array( + 'id1' => "$base/urla", + 'id2' => "$base/urlb", + 'id3' => "$base/urlc" + ); + foreach ($urls as $id => $url) { - elgg_register_js($url, $id); + elgg_register_js($id, $url); + elgg_load_js($id); } - $js_urls = elgg_get_js('head'); - $this->assertIdentical($js_urls[0], $urls['id1']); - $this->assertIdentical($js_urls[1], $urls['id2']); - $this->assertIdentical($js_urls[2], $urls['id3']); + $js_urls = elgg_get_loaded_js('head'); + + $this->assertIdentical($js_urls[500], $urls['id1']); + $this->assertIdentical($js_urls[501], $urls['id2']); + $this->assertIdentical($js_urls[502], $urls['id3']); - $js_urls = elgg_get_js('footer'); + $js_urls = elgg_get_loaded_js('footer'); $this->assertIdentical(array(), $js_urls); } -} + + // test ElggPriorityList + public function testElggPriorityListAdd() { + $pl = new ElggPriorityList(); + $elements = array( + 'Test value', + 'Test value 2', + 'Test value 3' + ); + + shuffle($elements); + + foreach ($elements as $element) { + $this->assertTrue($pl->add($element) !== false); + } + + $test_elements = $pl->getElements(); + + $this->assertTrue(is_array($test_elements)); + + foreach ($test_elements as $i => $element) { + // should be in the array + $this->assertTrue(in_array($element, $elements)); + + // should be the only element, so priority 0 + $this->assertEqual($i, array_search($element, $elements)); + } + } + + public function testElggPriorityListAddWithPriority() { + $pl = new ElggPriorityList(); + + $elements = array( + 10 => 'Test Element 10', + 5 => 'Test Element 5', + 0 => 'Test Element 0', + 100 => 'Test Element 100', + -1 => 'Test Element -1', + -5 => 'Test Element -5' + ); + + foreach ($elements as $priority => $element) { + $pl->add($element, $priority); + } + + $test_elements = $pl->getElements(); + + // should be sorted by priority + $elements_sorted = array( + -5 => 'Test Element -5', + -1 => 'Test Element -1', + 0 => 'Test Element 0', + 5 => 'Test Element 5', + 10 => 'Test Element 10', + 100 => 'Test Element 100', + ); + + $this->assertIdentical($elements_sorted, $test_elements); + + foreach ($test_elements as $priority => $element) { + $this->assertIdentical($elements[$priority], $element); + } + } + + public function testElggPriorityListGetNextPriority() { + $pl = new ElggPriorityList(); + + $elements = array( + 2 => 'Test Element', + 0 => 'Test Element 2', + -2 => 'Test Element 3', + ); + + foreach ($elements as $priority => $element) { + $pl->add($element, $priority); + } + + // we're not specifying a priority so it should be the next consecutive to 0. + $this->assertEqual(1, $pl->getNextPriority()); + + // add another one at priority 1 + $pl->add('Test Element 1'); + + // next consecutive to 0 is now 3. + $this->assertEqual(3, $pl->getNextPriority()); + } + + public function testElggPriorityListRemove() { + $pl = new ElggPriorityList(); + + $elements = array(); + for ($i=0; $i<3; $i++) { + $element = new stdClass(); + $element->name = "Test Element $i"; + $element->someAttribute = rand(0, 9999); + $elements[] = $element; + $pl->add($element); + } + + $pl->remove($elements[1]); + + $test_elements = $pl->getElements(); + + // make sure it's gone. + $this->assertEqual(2, count($test_elements)); + $this->assertIdentical($elements[0], $test_elements[0]); + $this->assertIdentical($elements[2], $test_elements[2]); + } + + public function testElggPriorityListMove() { + $pl = new ElggPriorityList(); + + $elements = array( + -5 => 'Test Element -5', + 0 => 'Test Element 0', + 5 => 'Test Element 5', + ); + + foreach ($elements as $priority => $element) { + $pl->add($element, $priority); + } + + $this->assertEqual($pl->move($elements[-5], 10), 10); + + // check it's at the new place + $this->assertIdentical($elements[-5], $pl->getElement(10)); + + // check it's not at the old + $this->assertFalse($pl->getElement(-5)); + } + + public function testElggPriorityListConstructor() { + $elements = array( + 10 => 'Test Element 10', + 5 => 'Test Element 5', + 0 => 'Test Element 0', + 100 => 'Test Element 100', + -1 => 'Test Element -1', + -5 => 'Test Element -5' + ); + + $pl = new ElggPriorityList($elements); + $test_elements = $pl->getElements(); + + $elements_sorted = array( + -5 => 'Test Element -5', + -1 => 'Test Element -1', + 0 => 'Test Element 0', + 5 => 'Test Element 5', + 10 => 'Test Element 10', + 100 => 'Test Element 100', + ); + + $this->assertIdentical($elements_sorted, $test_elements); + } + + public function testElggPriorityListGetPriority() { + $pl = new ElggPriorityList(); + + $elements = array( + 'Test element 0', + 'Test element 1', + 'Test element 2', + ); + + foreach ($elements as $element) { + $pl->add($element); + } + + $this->assertIdentical(0, $pl->getPriority($elements[0])); + $this->assertIdentical(1, $pl->getPriority($elements[1])); + $this->assertIdentical(2, $pl->getPriority($elements[2])); + } + + public function testElggPriorityListGetElement() { + $pl = new ElggPriorityList(); + $priorities = array(); + + $elements = array( + 'Test element 0', + 'Test element 1', + 'Test element 2', + ); + + foreach ($elements as $element) { + $priorities[] = $pl->add($element); + } + + $this->assertIdentical($elements[0], $pl->getElement($priorities[0])); + $this->assertIdentical($elements[1], $pl->getElement($priorities[1])); + $this->assertIdentical($elements[2], $pl->getElement($priorities[2])); + } + + public function testElggPriorityListPriorityCollision() { + $pl = new ElggPriorityList(); + + $elements = array( + 5 => 'Test element 5', + 6 => 'Test element 6', + 0 => 'Test element 0', + ); + + foreach ($elements as $priority => $element) { + $pl->add($element, $priority); + } + + // add at a colliding priority + $pl->add('Colliding element', 5); + + // should float to the top closest to 5, so 7 + $this->assertEqual(7, $pl->getPriority('Colliding element')); + } + + public function testElggPriorityListIterator() { + $elements = array( + -5 => 'Test element -5', + 0 => 'Test element 0', + 5 => 'Test element 5' + ); + + $pl = new ElggPriorityList($elements); + + foreach ($pl as $priority => $element) { + $this->assertIdentical($elements[$priority], $element); + } + } + + public function testElggPriorityListCountable() { + $pl = new ElggPriorityList(); + + $this->assertEqual(0, count($pl)); + + $pl->add('Test element 0'); + $this->assertEqual(1, count($pl)); + + $pl->add('Test element 1'); + $this->assertEqual(2, count($pl)); + + $pl->add('Test element 2'); + $this->assertEqual(3, count($pl)); + } + + public function testElggPriorityListUserSort() { + $elements = array( + 'A', + 'B', + 'C', + 'D', + 'E', + ); + + $elements_sorted_string = $elements; + + shuffle($elements); + $pl = new ElggPriorityList($elements); + + // will sort by priority + $test_elements = $pl->getElements(); + $this->assertIdentical($elements, $test_elements); + + function test_sort($elements) { + sort($elements, SORT_LOCALE_STRING); + return $elements; + } + + // force a new sort using our function + $pl->sort('test_sort'); + $test_elements = $pl->getElements(); + + $this->assertIdentical($elements_sorted_string, $test_elements); + } + + // see https://github.com/elgg/elgg/issues/4288 + public function testElggBatchIncOffset() { + // normal increment + $options = array( + 'offset' => 0, + 'limit' => 11 + ); + $batch = new ElggBatch(array('ElggCoreHelpersTest', 'elgg_batch_callback_test'), $options, + null, 5); + $j = 0; + foreach ($batch as $e) { + $offset = floor($j / 5) * 5; + $this->assertEqual($offset, $e['offset']); + $this->assertEqual($j + 1, $e['index']); + $j++; + } + + $this->assertEqual(11, $j); + + // no increment, 0 start + ElggCoreHelpersTest::elgg_batch_callback_test(array(), true); + $options = array( + 'offset' => 0, + 'limit' => 11 + ); + $batch = new ElggBatch(array('ElggCoreHelpersTest', 'elgg_batch_callback_test'), $options, + null, 5); + $batch->setIncrementOffset(false); + + $j = 0; + foreach ($batch as $e) { + $this->assertEqual(0, $e['offset']); + // should always be the same 5 + $this->assertEqual($e['index'], $j + 1 - (floor($j / 5) * 5)); + $j++; + } + $this->assertEqual(11, $j); + + // no increment, 3 start + ElggCoreHelpersTest::elgg_batch_callback_test(array(), true); + $options = array( + 'offset' => 3, + 'limit' => 11 + ); + $batch = new ElggBatch(array('ElggCoreHelpersTest', 'elgg_batch_callback_test'), $options, + null, 5); + $batch->setIncrementOffset(false); + + $j = 0; + foreach ($batch as $e) { + $this->assertEqual(3, $e['offset']); + // same 5 results + $this->assertEqual($e['index'], $j + 4 - (floor($j / 5) * 5)); + $j++; + } + + $this->assertEqual(11, $j); + } + + public function testElggBatchReadHandlesBrokenEntities() { + $num_test_entities = 8; + $guids = array(); + for ($i = $num_test_entities; $i > 0; $i--) { + $entity = new ElggObject(); + $entity->type = 'object'; + $entity->subtype = 'test_5357_subtype'; + $entity->access_id = ACCESS_PUBLIC; + $entity->save(); + $guids[] = $entity->guid; + _elgg_invalidate_cache_for_entity($entity->guid); + } + + // break entities such that the first fetch has one incomplete + // and the second and third fetches have only incompletes! + $db_prefix = elgg_get_config('dbprefix'); + delete_data(" + DELETE FROM {$db_prefix}objects_entity + WHERE guid IN ({$guids[1]}, {$guids[2]}, {$guids[3]}, {$guids[4]}, {$guids[5]}) + "); + + $options = array( + 'type' => 'object', + 'subtype' => 'test_5357_subtype', + 'order_by' => 'e.guid', + ); + + $entities_visited = array(); + $batch = new ElggBatch('elgg_get_entities', $options, null, 2); + /* @var ElggEntity[] $batch */ + foreach ($batch as $entity) { + $entities_visited[] = $entity->guid; + } + + // The broken entities should not have been visited + $this->assertEqual($entities_visited, array($guids[0], $guids[6], $guids[7])); + + // cleanup (including leftovers from previous tests) + $entity_rows = elgg_get_entities(array_merge($options, array( + 'callback' => '', + 'limit' => false, + ))); + $guids = array(); + foreach ($entity_rows as $row) { + $guids[] = $row->guid; + } + delete_data("DELETE FROM {$db_prefix}entities WHERE guid IN (" . implode(',', $guids) . ")"); + delete_data("DELETE FROM {$db_prefix}objects_entity WHERE guid IN (" . implode(',', $guids) . ")"); + } + + public function testElggBatchDeleteHandlesBrokenEntities() { + $num_test_entities = 8; + $guids = array(); + for ($i = $num_test_entities; $i > 0; $i--) { + $entity = new ElggObject(); + $entity->type = 'object'; + $entity->subtype = 'test_5357_subtype'; + $entity->access_id = ACCESS_PUBLIC; + $entity->save(); + $guids[] = $entity->guid; + _elgg_invalidate_cache_for_entity($entity->guid); + } + + // break entities such that the first fetch has one incomplete + // and the second and third fetches have only incompletes! + $db_prefix = elgg_get_config('dbprefix'); + delete_data(" + DELETE FROM {$db_prefix}objects_entity + WHERE guid IN ({$guids[1]}, {$guids[2]}, {$guids[3]}, {$guids[4]}, {$guids[5]}) + "); + + $options = array( + 'type' => 'object', + 'subtype' => 'test_5357_subtype', + 'order_by' => 'e.guid', + ); + + $entities_visited = array(); + $batch = new ElggBatch('elgg_get_entities', $options, null, 2, false); + /* @var ElggEntity[] $batch */ + foreach ($batch as $entity) { + $entities_visited[] = $entity->guid; + $entity->delete(); + } + + // The broken entities should not have been visited + $this->assertEqual($entities_visited, array($guids[0], $guids[6], $guids[7])); + + // cleanup (including leftovers from previous tests) + $entity_rows = elgg_get_entities(array_merge($options, array( + 'callback' => '', + 'limit' => false, + ))); + $guids = array(); + foreach ($entity_rows as $row) { + $guids[] = $row->guid; + } + delete_data("DELETE FROM {$db_prefix}entities WHERE guid IN (" . implode(',', $guids) . ")"); + delete_data("DELETE FROM {$db_prefix}objects_entity WHERE guid IN (" . implode(',', $guids) . ")"); + } + + static function elgg_batch_callback_test($options, $reset = false) { + static $count = 1; + + if ($reset) { + $count = 1; + return true; + } + + if ($count > 20) { + return false; + } + + for ($j = 0; ($options['limit'] < 5) ? $j < $options['limit'] : $j < 5; $j++) { + $return[] = array( + 'offset' => $options['offset'], + 'limit' => $options['limit'], + 'count' => $count++, + 'index' => 1 + $options['offset'] + $j + ); + } + + return $return; + } +}
\ No newline at end of file diff --git a/engine/tests/api/metadata.php b/engine/tests/api/metadata.php new file mode 100644 index 000000000..d23510c6a --- /dev/null +++ b/engine/tests/api/metadata.php @@ -0,0 +1,230 @@ +<?php +/** + * Elgg Test metadata API + * + * @package Elgg + * @subpackage Test + */ +class ElggCoreMetadataAPITest extends ElggCoreUnitTest { + protected $metastrings; + + /** + * Called before each test method. + */ + public function setUp() { + $this->metastrings = array(); + $this->object = new ElggObject(); + } + + /** + * Called after each test method. + */ + public function tearDown() { + // do not allow SimpleTest to interpret Elgg notices as exceptions + $this->swallowErrors(); + + unset($this->object); + } + + public function testGetMetastringById() { + foreach (array('metaUnitTest', 'metaunittest', 'METAUNITTEST') as $string) { + // since there is no guarantee that metastrings are garbage collected + // between unit test runs, we delete before testing + $this->delete_metastrings($string); + $this->create_metastring($string); + } + + // lookup metastring id + $cs_ids = get_metastring_id('metaUnitTest', TRUE); + $this->assertEqual($cs_ids, $this->metastrings['metaUnitTest']); + + // lookup all metastrings, ignoring case + $cs_ids = get_metastring_id('metaUnitTest', FALSE); + $this->assertEqual(count($cs_ids), 3); + $this->assertEqual(count($cs_ids), count($this->metastrings)); + foreach ($cs_ids as $string ) + { + $this->assertTrue(in_array($string, $this->metastrings)); + } + } + + public function testElggGetEntitiesFromMetadata() { + global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE; + $METASTRINGS_CACHE = $METASTRINGS_DEADNAME_CACHE = array(); + + $this->object->title = 'Meta Unit Test'; + $this->object->save(); + $this->create_metastring('metaUnitTest'); + $this->create_metastring('tested'); + + // create_metadata returns id of metadata on success + $this->assertNotEqual(false, create_metadata($this->object->guid, 'metaUnitTest', 'tested')); + + // check value with improper case + $options = array('metadata_names' => 'metaUnitTest', 'metadata_values' => 'Tested', 'limit' => 10, 'metadata_case_sensitive' => TRUE); + $this->assertIdentical(array(), elgg_get_entities_from_metadata($options)); + + // compare forced case with ignored case + $options = array('metadata_names' => 'metaUnitTest', 'metadata_values' => 'tested', 'limit' => 10, 'metadata_case_sensitive' => TRUE); + $case_true = elgg_get_entities_from_metadata($options); + $this->assertIsA($case_true, 'array'); + + $options = array('metadata_names' => 'metaUnitTest', 'metadata_values' => 'Tested', 'limit' => 10, 'metadata_case_sensitive' => FALSE); + $case_false = elgg_get_entities_from_metadata($options); + $this->assertIsA($case_false, 'array'); + + $this->assertIdentical($case_true, $case_false); + + // clean up + $this->object->delete(); + } + + public function testElggGetMetadataCount() { + $this->object->title = 'Meta Unit Test'; + $this->object->save(); + + $guid = $this->object->getGUID(); + create_metadata($guid, 'tested', 'tested1', 'text', 0, ACCESS_PUBLIC, true); + create_metadata($guid, 'tested', 'tested2', 'text', 0, ACCESS_PUBLIC, true); + + $count = (int)elgg_get_metadata(array( + 'metadata_names' => array('tested'), + 'guid' => $guid, + 'count' => true, + )); + + $this->assertIdentical($count, 2); + + $this->object->delete(); + } + + public function testElggDeleteMetadata() { + $e = new ElggObject(); + $e->save(); + + for ($i = 0; $i < 30; $i++) { + $name = "test_metadata$i"; + $e->$name = rand(0, 10000); + } + + $options = array( + 'guid' => $e->getGUID(), + 'limit' => 0, + ); + + $md = elgg_get_metadata($options); + $this->assertIdentical(30, count($md)); + + $this->assertTrue(elgg_delete_metadata($options)); + + $md = elgg_get_metadata($options); + $this->assertTrue(empty($md)); + + $e->delete(); + } + + /** + * https://github.com/Elgg/Elgg/issues/4867 + */ + public function testElggGetEntityMetadataWhereSqlWithFalseValue() { + $pair = array('name' => 'test' , 'value' => false); + $result = elgg_get_entity_metadata_where_sql('e', 'metadata', null, null, $pair); + $where = preg_replace( '/\s+/', ' ', $result['wheres'][0]); + $this->assertTrue(strpos($where, "msn1.string = 'test' AND BINARY msv1.string = 0") > 0); + + $result = elgg_get_entity_metadata_where_sql('e', 'metadata', array('test'), array(false)); + $where = preg_replace( '/\s+/', ' ', $result['wheres'][0]); + $this->assertTrue(strpos($where, "msn.string IN ('test')) AND ( BINARY msv.string IN ('0')")); + } + + // Make sure metadata with multiple values is correctly deleted when re-written + // by another user + // https://github.com/elgg/elgg/issues/2776 + public function test_elgg_metadata_multiple_values() { + $u1 = new ElggUser(); + $u1->username = rand(); + $u1->save(); + + $u2 = new ElggUser(); + $u2->username = rand(); + $u2->save(); + + $obj = new ElggObject(); + $obj->owner_guid = $u1->guid; + $obj->container_guid = $u1->guid; + $obj->access_id = ACCESS_PUBLIC; + $obj->save(); + + $md_values = array( + 'one', + 'two', + 'three' + ); + + // need to fake different logins. + // good times without mocking. + $original_user = elgg_get_logged_in_user_entity(); + $_SESSION['user'] = $u1; + + elgg_set_ignore_access(false); + + // add metadata as one user + $obj->test = $md_values; + + // check only these md exists + $db_prefix = elgg_get_config('dbprefix'); + $q = "SELECT * FROM {$db_prefix}metadata WHERE entity_guid = $obj->guid"; + $data = get_data($q); + + $this->assertEqual(count($md_values), count($data)); + foreach ($data as $md_row) { + $md = elgg_get_metadata_from_id($md_row->id); + $this->assertTrue(in_array($md->value, $md_values)); + $this->assertEqual('test', $md->name); + } + + // add md w/ same name as a different user + $_SESSION['user'] = $u2; + $md_values2 = array( + 'four', + 'five', + 'six', + 'seven' + ); + + $obj->test = $md_values2; + + $q = "SELECT * FROM {$db_prefix}metadata WHERE entity_guid = $obj->guid"; + $data = get_data($q); + + $this->assertEqual(count($md_values2), count($data)); + foreach ($data as $md_row) { + $md = elgg_get_metadata_from_id($md_row->id); + $this->assertTrue(in_array($md->value, $md_values2)); + $this->assertEqual('test', $md->name); + } + + $_SESSION['user'] = $original_user; + + $obj->delete(); + $u1->delete(); + $u2->delete(); + } + + protected function delete_metastrings($string) { + global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE; + $METASTRINGS_CACHE = $METASTRINGS_DEADNAME_CACHE = array(); + + $string = sanitise_string($string); + mysql_query("DELETE FROM {$CONFIG->dbprefix}metastrings WHERE string = BINARY '$string'"); + } + + protected function create_metastring($string) { + global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE; + $METASTRINGS_CACHE = $METASTRINGS_DEADNAME_CACHE = array(); + + $string = sanitise_string($string); + mysql_query("INSERT INTO {$CONFIG->dbprefix}metastrings (string) VALUES ('$string')"); + $this->metastrings[$string] = mysql_insert_id(); + } +} diff --git a/engine/tests/api/metadata_cache.php b/engine/tests/api/metadata_cache.php new file mode 100644 index 000000000..7fb328169 --- /dev/null +++ b/engine/tests/api/metadata_cache.php @@ -0,0 +1,176 @@ +<?php +/** + * Elgg Test metadata cache + * + * @package Elgg + * @subpackage Test + */ +class ElggCoreMetadataCacheTest extends ElggCoreUnitTest { + + /** + * @var ElggVolatileMetadataCache + */ + protected $cache; + + /** + * @var ElggObject + */ + protected $obj1; + + /** + * @var int + */ + protected $guid1; + + /** + * @var ElggObject + */ + protected $obj2; + + /** + * @var int + */ + protected $guid2; + + protected $name = 'test'; + protected $value = 'test'; + protected $ignoreAccess; + + /** + * Called before each test method. + */ + public function setUp() { + $this->ignoreAccess = elgg_set_ignore_access(false); + + $this->cache = elgg_get_metadata_cache(); + + $this->obj1 = new ElggObject(); + $this->obj1->save(); + $this->guid1 = $this->obj1->guid; + + $this->obj2 = new ElggObject(); + $this->obj2->save(); + $this->guid2 = $this->obj2->guid; + } + + /** + * Called after each test method. + */ + public function tearDown() { + $this->obj1->delete(); + $this->obj2->delete(); + + elgg_set_ignore_access($this->ignoreAccess); + } + + public function testBasicApi() { + // test de-coupled instance + $cache = new ElggVolatileMetadataCache(); + $cache->setIgnoreAccess(false); + $guid = 1; + + $this->assertFalse($cache->isKnown($guid, $this->name)); + + $cache->markEmpty($guid, $this->name); + $this->assertTrue($cache->isKnown($guid, $this->name)); + $this->assertNull($cache->load($guid, $this->name)); + + $cache->markUnknown($guid, $this->name); + $this->assertFalse($cache->isKnown($guid, $this->name)); + + $cache->save($guid, $this->name, $this->value); + $this->assertIdentical($cache->load($guid, $this->name), $this->value); + + $cache->save($guid, $this->name, 1, true); + $this->assertIdentical($cache->load($guid, $this->name), array($this->value, 1)); + + $cache->clear($guid); + $this->assertFalse($cache->isKnown($guid, $this->name)); + } + + public function testReadsAreCached() { + // test that reads fill cache + $this->obj1->setMetaData($this->name, $this->value); + $this->cache->flush(); + + $this->obj1->getMetaData($this->name); + $this->assertIdentical($this->cache->load($this->guid1, $this->name), $this->value); + } + + public function testWritesAreCached() { + // delete should mark cache as known to be empty + $this->obj1->deleteMetadata($this->name); + $this->assertTrue($this->cache->isKnown($this->guid1, $this->name)); + $this->assertNull($this->cache->load($this->guid1, $this->name)); + + // without name, delete should invalidate the entire entity + $this->cache->save($this->guid1, $this->name, $this->value); + elgg_delete_metadata(array( + 'guid' => $this->guid1, + )); + $this->assertFalse($this->cache->isKnown($this->guid1, $this->name)); + + // test set + $this->obj1->setMetaData($this->name, $this->value); + $this->assertIdentical($this->cache->load($this->guid1, $this->name), $this->value); + + // test set multiple + $this->obj1->setMetaData($this->name, 1, 'integer', true); + $this->assertIdentical($this->cache->load($this->guid1, $this->name), array($this->value, 1)); + + // writes when access is ignore should invalidate + $tmp_ignore = elgg_set_ignore_access(true); + $this->obj1->setMetaData($this->name, $this->value); + $this->assertFalse($this->cache->isKnown($this->guid1, $this->name)); + elgg_set_ignore_access($tmp_ignore); + } + + public function testDisableAndEnable() { + // both should mark cache unknown + $this->obj1->setMetaData($this->name, $this->value); + $this->obj1->disableMetadata($this->name); + $this->assertFalse($this->cache->isKnown($this->guid1, $this->name)); + + $this->cache->save($this->guid1, $this->name, $this->value); + $this->obj1->enableMetadata($this->name); + $this->assertFalse($this->cache->isKnown($this->guid1, $this->name)); + } + + public function testPopulateFromEntities() { + // test populating cache from set of entities + $this->obj1->setMetaData($this->name, $this->value); + $this->obj1->setMetaData($this->name, 4, 'integer', true); + $this->obj1->setMetaData("{$this->name}-2", "{$this->value}-2"); + $this->obj2->setMetaData($this->name, $this->value); + + $this->cache->flush(); + $this->cache->populateFromEntities(array($this->guid1, $this->guid2)); + + $expected = array(); + $expected[$this->name][] = $this->value; + $expected[$this->name][] = 4; + $expected["{$this->name}-2"] = "{$this->value}-2"; + $this->assertIdentical($this->cache->loadAll($this->guid1), $expected); + + $expected = array(); + $expected[$this->name] = $this->value; + $this->assertIdentical($this->cache->loadAll($this->guid2), $expected); + } + + public function testFilterHeavyEntities() { + $big_str = str_repeat('-', 5000); + $this->obj2->setMetaData($this->name, array($big_str, $big_str)); + + $guids = array($this->guid1, $this->guid2); + $expected = array($this->guid1); + $actual = $this->cache->filterMetadataHeavyEntities($guids, 6000); + $this->assertIdentical($actual, $expected); + } + + public function testCreateMetadataInvalidates() { + $this->obj1->foo = 1; + create_metadata($this->guid1, 'foo', 2, '', elgg_get_logged_in_user_guid(), ACCESS_FRIENDS); + + $this->assertEqual($this->obj1->foo, 2); + } +} diff --git a/engine/tests/api/metastrings.php b/engine/tests/api/metastrings.php new file mode 100644 index 000000000..5efdab972 --- /dev/null +++ b/engine/tests/api/metastrings.php @@ -0,0 +1,217 @@ +<?php +/** + * Elgg Metastrings test + * + * @package Elgg.Core + * @subpackage Metastrings.Test + */ +class ElggCoreMetastringsTest extends ElggCoreUnitTest { + + public $metastringTypes = array('metadata', 'annotations'); + + /** + * Called before each test object. + */ + public function __construct() { + parent::__construct(); + + $this->metastrings = array(); + $this->object = new ElggObject(); + $this->object->save(); + } + + public function createAnnotations($max = 1) { + $annotations = array(); + for ($i=0; $i<$max; $i++) { + $name = 'test_annotation_name' . rand(); + $value = 'test_annotation_value' . rand(); + $id = create_annotation($this->object->guid, $name, $value); + $annotations[] = $id; + } + + return $annotations; + } + + public function createMetadata($max = 1) { + $metadata = array(); + for ($i=0; $i<$max; $i++) { + $name = 'test_metadata_name' . rand(); + $value = 'test_metadata_value' . rand(); + $id = create_metadata($this->object->guid, $name, $value); + $metadata[] = $id; + } + + return $metadata; + } + + /** + * Called before each test method. + */ + public function setUp() { + + } + + /** + * Called after each test method. + */ + public function tearDown() { + access_show_hidden_entities(true); + elgg_delete_annotations(array( + 'guid' => $this->object->guid, + )); + access_show_hidden_entities(false); + } + + /** + * Called after each test object. + */ + public function __destruct() { + $this->object->delete(); + + parent::__destruct(); + } + + public function testDeleteByID() { + $db_prefix = elgg_get_config('dbprefix'); + $annotations = $this->createAnnotations(1); + $metadata = $this->createMetadata(1); + + foreach ($this->metastringTypes as $type) { + $id = ${$type}[0]; + $table = $db_prefix . $type; + $q = "SELECT * FROM $table WHERE id = $id"; + $test = get_data($q); + + $this->assertEqual($test[0]->id, $id); + $this->assertIdentical(true, elgg_delete_metastring_based_object_by_id($id, $type)); + $this->assertIdentical(array(), get_data($q)); + } + } + + public function testGetMetastringObjectFromID() { + $db_prefix = elgg_get_config('dbprefix'); + $annotations = $this->createAnnotations(1); + $metadata = $this->createMetadata(1); + + foreach ($this->metastringTypes as $type) { + $id = ${$type}[0]; + $test = elgg_get_metastring_based_object_from_id($id, $type); + + $this->assertEqual($id, $test->id); + } + } + + public function testGetMetastringObjectFromIDWithDisabledAnnotation() { + $name = 'test_annotation_name' . rand(); + $value = 'test_annotation_value' . rand(); + $id = create_annotation($this->object->guid, $name, $value); + $annotation = elgg_get_annotation_from_id($id); + $this->assertTrue($annotation->disable()); + + $test = elgg_get_metastring_based_object_from_id($id, 'annotation'); + $this->assertEqual(false, $test); + } + + public function testGetMetastringBasedObjectWithDisabledAnnotation() { + $name = 'test_annotation_name' . rand(); + $value = 'test_annotation_value' . rand(); + $id = create_annotation($this->object->guid, $name, $value); + $annotation = elgg_get_annotation_from_id($id); + $this->assertTrue($annotation->disable()); + + $test = elgg_get_metastring_based_objects(array( + 'metastring_type' => 'annotations', + 'guid' => $this->object->guid, + )); + $this->assertEqual(array(), $test); + } + + public function testEnableDisableByID() { + $db_prefix = elgg_get_config('dbprefix'); + $annotations = $this->createAnnotations(1); + $metadata = $this->createMetadata(1); + + foreach ($this->metastringTypes as $type) { + $id = ${$type}[0]; + $table = $db_prefix . $type; + $q = "SELECT * FROM $table WHERE id = $id"; + $test = get_data($q); + + // disable + $this->assertEqual($test[0]->enabled, 'yes'); + $this->assertTrue(elgg_set_metastring_based_object_enabled_by_id($id, 'no', $type)); + + $test = get_data($q); + $this->assertEqual($test[0]->enabled, 'no'); + + // enable + $ashe = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $this->assertTrue(elgg_set_metastring_based_object_enabled_by_id($id, 'yes', $type)); + + $test = get_data($q); + $this->assertEqual($test[0]->enabled, 'yes'); + + access_show_hidden_entities($ashe); + } + } + + public function testKeepMeFromDeletingEverything() { + foreach ($this->metastringTypes as $type) { + $required = array( + 'guid', 'guids' + ); + + switch ($type) { + case 'metadata': + $metadata_required = array( + 'metadata_owner_guid', 'metadata_owner_guids', + 'metadata_name', 'metadata_names', + 'metadata_value', 'metadata_values' + ); + + $required = array_merge($required, $metadata_required); + break; + + case 'annotations': + $annotations_required = array( + 'annotation_owner_guid', 'annotation_owner_guids', + 'annotation_name', 'annotation_names', + 'annotation_value', 'annotation_values' + ); + + $required = array_merge($required, $annotations_required); + break; + } + + $options = array(); + $this->assertFalse(elgg_is_valid_options_for_batch_operation($options, $type)); + + // limit alone isn't valid: + $options = array('limit' => 10); + $this->assertFalse(elgg_is_valid_options_for_batch_operation($options, $type)); + + foreach ($required as $key) { + $options = array(); + + $options[$key] = ELGG_ENTITIES_ANY_VALUE; + $this->assertFalse(elgg_is_valid_options_for_batch_operation($options, $type), "Sent $key = ELGG_ENTITIES_ANY_VALUE"); + + $options[$key] = ELGG_ENTITIES_NO_VALUE; + $this->assertFalse(elgg_is_valid_options_for_batch_operation($options, $type), "Sent $key = ELGG_ENTITIES_NO_VALUE"); + + $options[$key] = false; + $this->assertFalse(elgg_is_valid_options_for_batch_operation($options, $type), "Sent $key = bool false"); + + $options[$key] = true; + $this->assertTrue(elgg_is_valid_options_for_batch_operation($options, $type), "Sent $key = bool true"); + + $options[$key] = 'test'; + $this->assertTrue(elgg_is_valid_options_for_batch_operation($options, $type), "Sent $key = 'test'"); + + $options[$key] = array('test'); + $this->assertTrue(elgg_is_valid_options_for_batch_operation($options, $type), "Sent $key = array('test')"); + } + } + } +} diff --git a/engine/tests/api/output.php b/engine/tests/api/output.php new file mode 100644 index 000000000..c3d5aa8c6 --- /dev/null +++ b/engine/tests/api/output.php @@ -0,0 +1,74 @@ +<?php +/** + * Test case for ElggAutoP functionality. + */ +class ElggCoreOutputAutoPTest extends ElggCoreUnitTest { + + /** + * @var ElggAutoP + */ + protected $_autop; + + public function setUp() { + $this->_autop = new ElggAutoP(); + } + + public function testDomRoundtrip() { + $d = dir(dirname(dirname(__FILE__)) . '/test_files/output/autop'); + $in = file_get_contents($d->path . "/domdoc_in.html"); + $exp = file_get_contents($d->path . "/domdoc_exp.html"); + $exp = $this->flattenString($exp); + + $doc = new DOMDocument(); + libxml_use_internal_errors(true); + $doc->loadHTML("<html><meta http-equiv='content-type' content='text/html; charset=utf-8'><body>" + . $in . '</body></html>'); + $serialized = $doc->saveHTML(); + list(,$out) = explode('<body>', $serialized, 2); + list($out) = explode('</body>', $out, 2); + $out = $this->flattenString($out); + + $this->assertEqual($exp, $out, "DOMDocument's parsing/serialization roundtrip"); + } + + public function testProcess() { + $data = $this->provider(); + foreach ($data as $row) { + list($test, $in, $exp) = $row; + $exp = $this->flattenString($exp); + $out = $this->_autop->process($in); + $out = $this->flattenString($out); + + $this->assertEqual($exp, $out, "Equality case {$test}"); + } + } + + public function provider() { + $d = dir(dirname(dirname(__FILE__)) . '/test_files/output/autop'); + $tests = array(); + while (false !== ($entry = $d->read())) { + if (preg_match('/^([a-z\\-]+)\.in\.html$/i', $entry, $m)) { + $tests[] = $m[1]; + } + } + + $data = array(); + foreach ($tests as $test) { + $data[] = array( + $test, + file_get_contents($d->path . '/' . "{$test}.in.html"), + file_get_contents($d->path . '/' . "{$test}.exp.html"), + ); + } + return $data; + } + + /** + * Different versions of PHP return different whitespace between tags. + * Removing all line breaks normalizes that. + */ + public function flattenString($string) { + $r = preg_replace('/[\n\r]+/', '', $string); + return $r; + } +}
\ No newline at end of file diff --git a/engine/tests/api/plugins.php b/engine/tests/api/plugins.php new file mode 100644 index 000000000..d0f111c48 --- /dev/null +++ b/engine/tests/api/plugins.php @@ -0,0 +1,299 @@ +<?php +/** + * Elgg Plugins Test + * + * @package Elgg.Core + * @subpackage Plugins.Test + */ +class ElggCorePluginsAPITest extends ElggCoreUnitTest { + // 1.8 manifest object + var $manifest18; + + // 1.8 package at test_files/plugin_18/ + var $package18; + + // 1.7 manifest object + var $manifest17; + + // 1.7 package at test_files/plugin_17/ + var $package17; + + public function __construct() { + parent::__construct(); + + $this->manifest18 = new ElggPluginManifest(get_config('path') . 'engine/tests/test_files/plugin_18/manifest.xml', 'plugin_test_18'); + $this->manifest17 = new ElggPluginManifest(get_config('path') . 'engine/tests/test_files/plugin_17/manifest.xml', 'plugin_test_17'); + + $this->package18 = new ElggPluginPackage(get_config('path') . 'engine/tests/test_files/plugin_18'); + $this->package17 = new ElggPluginPackage(get_config('path') . 'engine/tests/test_files/plugin_17'); + } + + /** + * Called after each test method. + */ + public function tearDown() { + // do not allow SimpleTest to interpret Elgg notices as exceptions + $this->swallowErrors(); + } + + // generic tests + public function testElggPluginManifestFromString() { + $manifest_file = file_get_contents(get_config('path') . 'engine/tests/test_files/plugin_17/manifest.xml'); + $manifest = new ElggPluginManifest($manifest_file); + + $this->assertIsA($manifest, 'ElggPluginManifest'); + } + + public function testElggPluginManifestFromFile() { + $file = get_config('path') . 'engine/tests/test_files/plugin_17/manifest.xml'; + $manifest = new ElggPluginManifest($file); + + $this->assertIsA($manifest, 'ElggPluginManifest'); + } + + public function testElggPluginManifestFromXMLEntity() { + $xml = xml_to_object($manifest_file = file_get_contents(get_config('path') . 'engine/tests/test_files/plugin_17/manifest.xml')); + $manifest = new ElggPluginManifest($xml); + + $this->assertIsA($manifest, 'ElggPluginManifest'); + } + + // exact manifest values + // 1.8 interface + public function testElggPluginManifest18() { + $manifest_array = array( + 'name' => 'Test Manifest', + 'author' => 'Anyone', + 'version' => '1.0', + 'blurb' => 'A concise description.', + 'description' => 'A longer, more interesting description.', + 'website' => 'http://www.elgg.org/', + 'repository' => 'https://github.com/Elgg/Elgg', + 'bugtracker' => 'https://github.com/elgg/elgg/issues', + 'donations' => 'http://elgg.org/supporter.php', + 'copyright' => '(C) Elgg Foundation 2011', + 'license' => 'GNU General Public License version 2', + + 'requires' => array( + array('type' => 'elgg_version', 'version' => '3009030802', 'comparison' => 'lt'), + array('type' => 'elgg_release', 'version' => '1.8-svn'), + array('type' => 'php_extension', 'name' => 'gd'), + array('type' => 'php_ini', 'name' => 'short_open_tag', 'value' => 'off'), + array('type' => 'php_extension', 'name' => 'made_up', 'version' => '1.0'), + array('type' => 'plugin', 'name' => 'fake_plugin', 'version' => '1.0'), + array('type' => 'plugin', 'name' => 'profile', 'version' => '1.0'), + array('type' => 'plugin', 'name' => 'profile_api', 'version' => '1.3', 'comparison' => 'lt'), + array('type' => 'priority', 'priority' => 'after', 'plugin' => 'profile'), + ), + + 'screenshot' => array( + array('description' => 'Fun things to do 1', 'path' => 'graphics/plugin_ss1.png'), + array('description' => 'Fun things to do 2', 'path' => 'graphics/plugin_ss2.png'), + ), + + 'category' => array( + 'Admin', 'ServiceAPI' + ), + + 'conflicts' => array( + array('type' => 'plugin', 'name' => 'profile_api', 'version' => '1.0') + ), + + 'provides' => array( + array('type' => 'plugin', 'name' => 'profile_api', 'version' => '1.3'), + array('type' => 'php_extension', 'name' => 'big_math', 'version' => '1.0') + ), + + 'suggests' => array( + array('type' => 'plugin', 'name' => 'facebook_connect', 'version' => '1.0'), + ), + + // string because we are reading from a file + 'activate_on_install' => 'true', + ); + + $this->assertIdentical($this->manifest18->getManifest(), $manifest_array); + } + + public function testElggPluginManifest17() { + $manifest_array = array( + 'author' => 'Anyone', + 'version' => '1.0', + 'description' => 'A 1.7-style manifest.', + 'website' => 'http://www.elgg.org/', + 'copyright' => '(C) Elgg Foundation 2011', + 'license' => 'GNU General Public License version 2', + 'elgg_version' => '2009030702', + 'name' => 'Plugin Test 17', + ); + + $this->assertIdentical($this->manifest17->getManifest(), $manifest_array); + } + + + public function testElggPluginManifestGetApiVersion() { + $this->assertEqual($this->manifest18->getApiVersion(), 1.8); + $this->assertEqual($this->manifest17->getApiVersion(), 1.7); + } + + public function testElggPluginManifestGetPluginID() { + $this->assertEqual($this->manifest18->getPluginID(), 'plugin_test_18'); + $this->assertEqual($this->manifest17->getPluginID(), 'plugin_test_17'); + } + + + // normalized attributes + public function testElggPluginManifestGetName() { + $this->assertEqual($this->manifest18->getName(), 'Test Manifest'); + $this->assertEqual($this->manifest17->getName(), 'Plugin Test 17'); + } + + public function testElggPluginManifestGetAuthor() { + $this->assertEqual($this->manifest18->getAuthor(), 'Anyone'); + $this->assertEqual($this->manifest17->getAuthor(), 'Anyone'); + } + + public function testElggPluginManifestGetVersion() { + $this->assertEqual($this->manifest18->getVersion(), 1.0); + $this->assertEqual($this->manifest17->getVersion(), 1.0); + } + + public function testElggPluginManifestGetBlurb() { + $this->assertEqual($this->manifest18->getBlurb(), 'A concise description.'); + $this->assertEqual($this->manifest17->getBlurb(), 'A 1.7-style manifest.'); + } + + public function testElggPluginManifestGetWebsite() { + $this->assertEqual($this->manifest18->getWebsite(), 'http://www.elgg.org/'); + $this->assertEqual($this->manifest17->getWebsite(), 'http://www.elgg.org/'); + } + + public function testElggPluginManifestGetRepository() { + $this->assertEqual($this->manifest18->getRepositoryURL(), 'https://github.com/Elgg/Elgg'); + $this->assertEqual($this->manifest17->getRepositoryURL(), ''); + } + + public function testElggPluginManifestGetBugtracker() { + $this->assertEqual($this->manifest18->getBugTrackerURL(), 'https://github.com/elgg/elgg/issues'); + $this->assertEqual($this->manifest17->getBugTrackerURL(), ''); + } + + public function testElggPluginManifestGetDonationsPage() { + $this->assertEqual($this->manifest18->getDonationsPageURL(), 'http://elgg.org/supporter.php'); + $this->assertEqual($this->manifest17->getDonationsPageURL(), ''); + } + + public function testElggPluginManifestGetCopyright() { + $this->assertEqual($this->manifest18->getCopyright(), '(C) Elgg Foundation 2011'); + $this->assertEqual($this->manifest18->getCopyright(), '(C) Elgg Foundation 2011'); + } + + public function testElggPluginManifestGetLicense() { + $this->assertEqual($this->manifest18->getLicense(), 'GNU General Public License version 2'); + $this->assertEqual($this->manifest17->getLicense(), 'GNU General Public License version 2'); + } + + + public function testElggPluginManifestGetRequires() { + $requires = array( + array('type' => 'elgg_version', 'version' => '3009030802', 'comparison' => 'lt'), + array('type' => 'elgg_release', 'version' => '1.8-svn', 'comparison' => 'ge'), + array('type' => 'php_extension', 'name' => 'gd', 'version' => '', 'comparison' => '='), + array('type' => 'php_ini', 'name' => 'short_open_tag', 'value' => 0, 'comparison' => '='), + array('type' => 'php_extension', 'name' => 'made_up', 'version' => '1.0', 'comparison' => '='), + array('type' => 'plugin', 'name' => 'fake_plugin', 'version' => '1.0', 'comparison' => 'ge'), + array('type' => 'plugin', 'name' => 'profile', 'version' => '1.0', 'comparison' => 'ge'), + array('type' => 'plugin', 'name' => 'profile_api', 'version' => '1.3', 'comparison' => 'lt'), + array('type' => 'priority', 'priority' => 'after', 'plugin' => 'profile'), + ); + + $this->assertIdentical($this->package18->getManifest()->getRequires(), $requires); + + $requires = array( + array('type' => 'elgg_version', 'version' => '2009030702', 'comparison' => 'ge') + ); + + $this->assertIdentical($this->package17->getManifest()->getRequires(), $requires); + } + + public function testElggPluginManifestGetSuggests() { + $suggests = array( + array('type' => 'plugin', 'name' => 'facebook_connect', 'version' => '1.0', 'comparison' => 'ge'), + ); + + $this->assertIdentical($this->package18->getManifest()->getSuggests(), $suggests); + + $suggests = array(); + + $this->assertIdentical($this->package17->getManifest()->getSuggests(), $suggests); + } + + public function testElggPluginManifestGetDescription() { + $this->assertEqual($this->package18->getManifest()->getDescription(), 'A longer, more interesting description.'); + $this->assertEqual($this->package17->getManifest()->getDescription(), 'A 1.7-style manifest.'); + } + + public function testElggPluginManifestGetCategories() { + $categories = array( + 'Admin', 'ServiceAPI' + ); + + $this->assertIdentical($this->package18->getManifest()->getCategories(), $categories); + $this->assertIdentical($this->package17->getManifest()->getCategories(), array()); + } + + public function testElggPluginManifestGetScreenshots() { + $screenshots = array( + array('description' => 'Fun things to do 1', 'path' => 'graphics/plugin_ss1.png'), + array('description' => 'Fun things to do 2', 'path' => 'graphics/plugin_ss2.png'), + ); + + $this->assertIdentical($this->package18->getManifest()->getScreenshots(), $screenshots); + $this->assertIdentical($this->package17->getManifest()->getScreenshots(), array()); + } + + public function testElggPluginManifestGetProvides() { + $provides = array( + array('type' => 'plugin', 'name' => 'profile_api', 'version' => '1.3'), + array('type' => 'php_extension', 'name' => 'big_math', 'version' => '1.0'), + array('type' => 'plugin', 'name' => 'plugin_18', 'version' => '1.0') + ); + + $this->assertIdentical($this->package18->getManifest()->getProvides(), $provides); + + + $provides = array( + array('type' => 'plugin', 'name' => 'plugin_17', 'version' => '1.0') + ); + + $this->assertIdentical($this->package17->getManifest()->getProvides(), $provides); + } + + public function testElggPluginManifestGetConflicts() { + $conflicts = array( + array( + 'type' => 'plugin', + 'name' => 'profile_api', + 'version' => '1.0', + 'comparison' => '=' + ) + ); + + $this->assertIdentical($this->manifest18->getConflicts(), $conflicts); + $this->assertIdentical($this->manifest17->getConflicts(), array()); + } + + public function testElggPluginManifestGetActivateOnInstall() { + $this->assertIdentical($this->manifest18->getActivateOnInstall(), true); + } + + // ElggPluginPackage + public function testElggPluginPackageDetectIDFromPath() { + $this->assertEqual($this->package18->getID(), 'plugin_18'); + } + + public function testElggPluginPackageDetectIDFromPluginID() { + $package = new ElggPluginPackage('profile'); + $this->assertEqual($package->getID(), 'profile'); + } +} diff --git a/engine/tests/api/river.php b/engine/tests/api/river.php new file mode 100644 index 000000000..6931b9f41 --- /dev/null +++ b/engine/tests/api/river.php @@ -0,0 +1,21 @@ +<?php +/** + * Elgg Test river api + * + * @package Elgg + * @subpackage Test + */ +class ElggCoreRiverAPITest extends ElggCoreUnitTest { + + public function testElggTypeSubtypeWhereSQL() { + $types = array('object'); + $subtypes = array('blog'); + $result = elgg_get_river_type_subtype_where_sql('rv', $types, $subtypes, null); + $this->assertIdentical($result, "((rv.type = 'object') AND ((rv.subtype = 'blog')))"); + + $types = array('object'); + $subtypes = array('blog', 'file'); + $result = elgg_get_river_type_subtype_where_sql('rv', $types, $subtypes, null); + $this->assertIdentical($result, "((rv.type = 'object') AND ((rv.subtype = 'blog') OR (rv.subtype = 'file')))"); + } +} diff --git a/engine/tests/objects/entities.php b/engine/tests/objects/entities.php index 018f7aa02..bac72079e 100644 --- a/engine/tests/objects/entities.php +++ b/engine/tests/objects/entities.php @@ -26,16 +26,16 @@ class ElggCoreEntityTest extends ElggCoreUnitTest { */ public function testElggEntityAttributes() { $test_attributes = array(); - $test_attributes['guid'] = ''; - $test_attributes['type'] = ''; - $test_attributes['subtype'] = ''; - $test_attributes['owner_guid'] = get_loggedin_userid(); - $test_attributes['container_guid'] = get_loggedin_userid(); - $test_attributes['site_guid'] = 0; + $test_attributes['guid'] = NULL; + $test_attributes['type'] = NULL; + $test_attributes['subtype'] = NULL; + $test_attributes['owner_guid'] = elgg_get_logged_in_user_guid(); + $test_attributes['container_guid'] = elgg_get_logged_in_user_guid(); + $test_attributes['site_guid'] = NULL; $test_attributes['access_id'] = ACCESS_PRIVATE; - $test_attributes['time_created'] = ''; - $test_attributes['time_updated'] = ''; - $test_attributes['last_action'] = ''; + $test_attributes['time_created'] = NULL; + $test_attributes['time_updated'] = NULL; + $test_attributes['last_action'] = NULL; $test_attributes['enabled'] = 'yes'; $test_attributes['tables_split'] = 1; $test_attributes['tables_loaded'] = 0; @@ -77,7 +77,7 @@ class ElggCoreEntityTest extends ElggCoreUnitTest { $this->assertIdentical($this->entity->getGUID(), $this->entity->guid ); $this->assertIdentical($this->entity->getType(), $this->entity->type ); $this->assertIdentical($this->entity->getSubtype(), $this->entity->subtype ); - $this->assertIdentical($this->entity->getOwner(), $this->entity->owner_guid ); + $this->assertIdentical($this->entity->getOwnerGUID(), $this->entity->owner_guid ); $this->assertIdentical($this->entity->getAccessID(), $this->entity->access_id ); $this->assertIdentical($this->entity->getTimeCreated(), $this->entity->time_created ); $this->assertIdentical($this->entity->getTimeUpdated(), $this->entity->time_updated ); @@ -89,25 +89,25 @@ class ElggCoreEntityTest extends ElggCoreUnitTest { $this->assertFalse(isset($this->entity->non_existent)); // create metadata - $this->assertTrue($this->entity->non_existent = 'testing'); + $this->entity->existent = 'testing'; + $this->assertIdentical($this->entity->existent, 'testing'); // check metadata set - $this->assertTrue(isset($this->entity->non_existent)); - $this->assertIdentical($this->entity->non_existent, 'testing'); - $this->assertIdentical($this->entity->getMetaData('non_existent'), 'testing'); + $this->assertTrue(isset($this->entity->existent)); + $this->assertIdentical($this->entity->getMetaData('existent'), 'testing'); // check internal metadata array $metadata = $this->entity->expose_metadata(); - $this->assertIdentical($metadata['non_existent'], 'testing'); + $this->assertIdentical($metadata['existent'], array('testing')); } public function testElggEnityGetAndSetAnnotations() { $this->assertFalse(array_key_exists('non_existent', $this->entity->expose_annotations())); - $this->assertFalse($this->entity->getAnnotations('non_existent')); + $this->assertIdentical($this->entity->getAnnotations('non_existent'), array()); // set and check temp annotation $this->assertTrue($this->entity->annotate('non_existent', 'testing')); - $this->assertIdentical($this->entity->getAnnotations('non_existent'), 'testing'); + $this->assertIdentical($this->entity->getAnnotations('non_existent'), array('testing')); $this->assertTrue(array_key_exists('non_existent', $this->entity->expose_annotations())); // save entity and check for annotation @@ -118,30 +118,27 @@ class ElggCoreEntityTest extends ElggCoreUnitTest { $this->assertIsA($annotations[0], 'ElggAnnotation'); $this->assertIdentical($annotations[0]->name, 'non_existent'); $this->assertEqual($this->entity->countAnnotations('non_existent'), 1); - - $this->assertIdentical($annotations, get_annotations($this->entity->getGUID())); - $this->assertIdentical($annotations, get_annotations($this->entity->getGUID(), 'site')); - $this->assertIdentical($annotations, get_annotations($this->entity->getGUID(), 'site', 'testing')); - $this->assertIdentical(FALSE, get_annotations($this->entity->getGUID(), 'site', 'fail')); + + $this->assertIdentical($annotations, elgg_get_annotations(array('guid' => $this->entity->getGUID()))); + $this->assertIdentical($annotations, elgg_get_annotations(array('guid' => $this->entity->getGUID(), 'type' => 'site'))); + $this->assertIdentical($annotations, elgg_get_annotations(array('guid' => $this->entity->getGUID(), 'type' => 'site', 'subtype' => 'testing'))); + $this->assertIdentical(FALSE, elgg_get_annotations(array('guid' => $this->entity->getGUID(), 'type' => 'site', 'subtype' => 'fail'))); // clear annotation - $this->assertTrue($this->entity->clearAnnotations()); + $this->assertTrue($this->entity->deleteAnnotations()); $this->assertEqual($this->entity->countAnnotations('non_existent'), 0); - - $this->assertIdentical(FALSE, get_annotations($this->entity->getGUID())); - $this->assertIdentical(FALSE, get_annotations($this->entity->getGUID(), 'site')); - $this->assertIdentical(FALSE, get_annotations($this->entity->getGUID(), 'site', 'testing')); + + $this->assertIdentical(array(), elgg_get_annotations(array('guid' => $this->entity->getGUID()))); + $this->assertIdentical(array(), elgg_get_annotations(array('guid' => $this->entity->getGUID(), 'type' => 'site'))); + $this->assertIdentical(array(), elgg_get_annotations(array('guid' => $this->entity->getGUID(), 'type' => 'site', 'subtype' => 'testing'))); // clean up $this->assertTrue($this->entity->delete()); + remove_subtype('site', 'testing'); } public function testElggEntityCache() { global $ENTITY_CACHE; - $ENTITY_CACHE = NULL; - - $this->assertNull($ENTITY_CACHE); - initialise_entity_cache(); $this->assertIsA($ENTITY_CACHE, 'array'); } @@ -181,7 +178,7 @@ class ElggCoreEntityTest extends ElggCoreUnitTest { $this->AssertEqual($this->entity->get('non_existent'), 'testing'); // clean up with delete - $this->assertTrue($this->entity->delete()); + $this->assertIdentical(true, $this->entity->delete()); } public function testElggEntityDisableAndEnable() { @@ -190,19 +187,104 @@ class ElggCoreEntityTest extends ElggCoreUnitTest { // ensure enabled $this->assertTrue($this->entity->isEnabled()); - // false on disable + // false on disable because it's not saved yet. $this->assertFalse($this->entity->disable()); // save and disable $this->save_entity(); + + // add annotations and metadata to check if they're disabled. + $annotation_id = create_annotation($this->entity->guid, 'test_annotation_' . rand(), 'test_value_' . rand()); + $metadata_id = create_metadata($this->entity->guid, 'test_metadata_' . rand(), 'test_value_' . rand()); + $this->assertTrue($this->entity->disable()); // ensure disabled by comparing directly with database $entity = get_data_row("SELECT * FROM {$CONFIG->dbprefix}entities WHERE guid = '{$this->entity->guid}'"); $this->assertIdentical($entity->enabled, 'no'); + $annotation = get_data_row("SELECT * FROM {$CONFIG->dbprefix}annotations WHERE id = '$annotation_id'"); + $this->assertIdentical($annotation->enabled, 'no'); + + $metadata = get_data_row("SELECT * FROM {$CONFIG->dbprefix}metadata WHERE id = '$metadata_id'"); + $this->assertIdentical($metadata->enabled, 'no'); + // re-enable for deletion to work $this->assertTrue($this->entity->enable()); + + // check enabled + // check annotations and metadata enabled. + $entity = get_data_row("SELECT * FROM {$CONFIG->dbprefix}entities WHERE guid = '{$this->entity->guid}'"); + $this->assertIdentical($entity->enabled, 'yes'); + + $annotation = get_data_row("SELECT * FROM {$CONFIG->dbprefix}annotations WHERE id = '$annotation_id'"); + $this->assertIdentical($annotation->enabled, 'yes'); + + $metadata = get_data_row("SELECT * FROM {$CONFIG->dbprefix}metadata WHERE id = '$metadata_id'"); + $this->assertIdentical($metadata->enabled, 'yes'); + + $this->assertTrue($this->entity->delete()); + } + + public function testElggEntityRecursiveDisableAndEnable() { + global $CONFIG; + + $this->save_entity(); + $obj1 = new ElggObject(); + $obj1->container_guid = $this->entity->getGUID(); + $obj1->save(); + $obj2 = new ElggObject(); + $obj2->container_guid = $this->entity->getGUID(); + $obj2->save(); + + // disable $obj2 before disabling the container + $this->assertTrue($obj2->disable()); + + // disable entities container by $this->entity + $this->assertTrue($this->entity->disable()); + $entity = get_data_row("SELECT * FROM {$CONFIG->dbprefix}entities WHERE guid = '{$obj1->guid}'"); + $this->assertIdentical($entity->enabled, 'no'); + + // enable entities that were disabled with the container (but not $obj2) + $this->assertTrue($this->entity->enable()); + $entity = get_data_row("SELECT * FROM {$CONFIG->dbprefix}entities WHERE guid = '{$obj1->guid}'"); + $this->assertIdentical($entity->enabled, 'yes'); + $entity = get_data_row("SELECT * FROM {$CONFIG->dbprefix}entities WHERE guid = '{$obj2->guid}'"); + $this->assertIdentical($entity->enabled, 'no'); + + // cleanup + $this->assertTrue($obj2->enable()); + $this->assertTrue($obj2->delete()); + $this->assertTrue($obj1->delete()); + $this->assertTrue($this->entity->delete()); + } + + public function testElggEntityMetadata() { + // let's delete a non-existent metadata + $this->assertFalse($this->entity->deleteMetadata('important')); + + // let's add the metadata + $this->entity->important = 'indeed!'; + $this->assertIdentical('indeed!', $this->entity->important); + $this->entity->less_important = 'true, too!'; + $this->assertIdentical('true, too!', $this->entity->less_important); + $this->save_entity(); + + // test deleting incorrectly + // @link https://github.com/elgg/elgg/issues/2273 + $this->assertNull($this->entity->deleteMetadata('impotent')); + $this->assertEqual($this->entity->important, 'indeed!'); + + // get rid of one metadata + $this->assertEqual($this->entity->important, 'indeed!'); + $this->assertTrue($this->entity->deleteMetadata('important')); + $this->assertNull($this->entity->important); + + // get rid of all metadata + $this->assertTrue($this->entity->deleteMetadata()); + $this->assertNull($this->entity->less_important); + + // clean up database $this->assertTrue($this->entity->delete()); } @@ -221,8 +303,101 @@ class ElggCoreEntityTest extends ElggCoreUnitTest { $this->assertIdentical($exportables, $this->entity->getExportableValues()); } - protected function save_entity($type='site') - { + public function testElggEntityMultipleMetadata() { + foreach (array(false, true) as $save) { + if ($save) { + $this->save_entity(); + } + $md = array('brett', 'bryan', 'brad'); + $name = 'test_md_' . rand(); + + $this->entity->$name = $md; + + $this->assertEqual($md, $this->entity->$name); + + if ($save) { + $this->assertTrue($this->entity->delete()); + } + } + } + + public function testElggEntitySingleElementArrayMetadata() { + foreach (array(false, true) as $save) { + if ($save) { + $this->save_entity(); + } + $md = array('test'); + $name = 'test_md_' . rand(); + + $this->entity->$name = $md; + + $this->assertEqual($md[0], $this->entity->$name); + + if ($save) { + $this->assertTrue($this->entity->delete()); + } + } + } + + public function testElggEntityAppendMetadata() { + foreach (array(false, true) as $save) { + if ($save) { + $this->save_entity(); + } + $md = 'test'; + $name = 'test_md_' . rand(); + + $this->entity->$name = $md; + $this->entity->setMetaData($name, 'test2', '', true); + + $this->assertEqual(array('test', 'test2'), $this->entity->$name); + + if ($save) { + $this->assertTrue($this->entity->delete()); + } + } + } + + public function testElggEntitySingleElementArrayAppendMetadata() { + foreach (array(false, true) as $save) { + if ($save) { + $this->save_entity(); + } + $md = 'test'; + $name = 'test_md_' . rand(); + + $this->entity->$name = $md; + $this->entity->setMetaData($name, array('test2'), '', true); + + $this->assertEqual(array('test', 'test2'), $this->entity->$name); + + if ($save) { + $this->assertTrue($this->entity->delete()); + } + } + } + + public function testElggEntityArrayAppendMetadata() { + foreach (array(false, true) as $save) { + if ($save) { + $this->save_entity(); + } + $md = array('brett', 'bryan', 'brad'); + $md2 = array('test1', 'test2', 'test3'); + $name = 'test_md_' . rand(); + + $this->entity->$name = $md; + $this->entity->setMetaData($name, $md2, '', true); + + $this->assertEqual(array_merge($md, $md2), $this->entity->$name); + + if ($save) { + $this->assertTrue($this->entity->delete()); + } + } + } + + protected function save_entity($type='site') { $this->entity->type = $type; $this->assertNotEqual($this->entity->save(), 0); } diff --git a/engine/tests/objects/metadata.php b/engine/tests/objects/metadata.php deleted file mode 100644 index b5b9aba02..000000000 --- a/engine/tests/objects/metadata.php +++ /dev/null @@ -1,106 +0,0 @@ -<?php -/** - * Elgg Test ElggMetadata - * - * @package Elgg - * @subpackage Test - */ -class ElggCoreMetadataTest extends ElggCoreUnitTest { - protected $metastrings; - - /** - * Called before each test method. - */ - public function setUp() { - $this->metastrings = array(); - $this->object = new ElggObject(); - } - - /** - * Called after each test method. - */ - public function tearDown() { - // do not allow SimpleTest to interpret Elgg notices as exceptions - $this->swallowErrors(); - - unset($this->object); - } - - public function testGetMetastringById() { - foreach (array('metaUnitTest', 'metaunittest', 'METAUNITTEST') as $string) { - $this->create_metastring($string); - } - - // lookup metastring id - $cs_ids = get_metastring_id('metaUnitTest', TRUE); - $this->assertEqual($cs_ids, $this->metastrings['metaUnitTest']); - - // lookup all metastrings, ignoring case - $cs_ids = get_metastring_id('metaUnitTest', FALSE); - $this->assertEqual(count($cs_ids), 3); - $this->assertEqual(count($cs_ids), count($this->metastrings)); - foreach ($cs_ids as $string ) - { - $this->assertTrue(in_array($string, $this->metastrings)); - } - - // clean up - $this->delete_metastrings(); - } - - public function testElggGetEntitiesFromMetadata() { - global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE; - $METASTRINGS_CACHE = $METASTRINGS_DEADNAME_CACHE = array(); - - $this->object->title = 'Meta Unit Test'; - $this->object->save(); - $this->create_metastring('metaUnitTest'); - $this->create_metastring('tested'); - - // create_metadata returns id of metadata on success - $this->assertTrue(create_metadata($this->object->guid, 'metaUnitTest', 'tested')); - - // check value with improper case - $options = array('metadata_names' => 'metaUnitTest', 'metadata_values' => 'Tested', 'limit' => 10, 'metadata_case_sensitive' => TRUE); - $this->assertFalse(elgg_get_entities_from_metadata($options)); - - // compare forced case with ignored case - $options = array('metadata_names' => 'metaUnitTest', 'metadata_values' => 'tested', 'limit' => 10, 'metadata_case_sensitive' => TRUE); - $case_true = elgg_get_entities_from_metadata($options); - $this->assertIsA($case_true, 'array'); - - $options = array('metadata_names' => 'metaUnitTest', 'metadata_values' => 'Tested', 'limit' => 10, 'metadata_case_sensitive' => FALSE); - $case_false = elgg_get_entities_from_metadata($options); - $this->assertIsA($case_false, 'array'); - - $this->assertIdentical($case_true, $case_false); - - // check deprecated get_entities_from_metadata() function - $deprecated = get_entities_from_metadata('metaUnitTest', 'tested', '', '', 0, 10, 0, '', 0, FALSE, TRUE); - $this->assertIdentical($deprecated, $case_true); - - // check entity list - //$this->dump(list_entities_from_metadata('metaUnitTest', 'Tested', '', '', 0, 10, TRUE, TRUE, TRUE, FALSE)); - - // clean up - $this->delete_metastrings(); - $this->object->delete(); - } - - - protected function create_metastring($string) { - global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE; - $METASTRINGS_CACHE = $METASTRINGS_DEADNAME_CACHE = array(); - - mysql_query("INSERT INTO {$CONFIG->dbprefix}metastrings (string) VALUES ('$string')"); - $this->metastrings[$string] = mysql_insert_id(); - } - - protected function delete_metastrings() { - global $CONFIG, $METASTRINGS_CACHE, $METASTRINGS_DEADNAME_CACHE; - $METASTRINGS_CACHE = $METASTRINGS_DEADNAME_CACHE = array(); - - $strings = implode(', ', $this->metastrings); - mysql_query("DELETE FROM {$CONFIG->dbprefix}metastrings WHERE id IN ($strings)"); - } -} diff --git a/engine/tests/objects/objects.php b/engine/tests/objects/objects.php index 40c3c635a..263ab2414 100644 --- a/engine/tests/objects/objects.php +++ b/engine/tests/objects/objects.php @@ -38,21 +38,21 @@ class ElggCoreObjectTest extends ElggCoreUnitTest { public function testElggObjectConstructor() { $attributes = array(); - $attributes['guid'] = ''; + $attributes['guid'] = NULL; $attributes['type'] = 'object'; - $attributes['subtype'] = ''; - $attributes['owner_guid'] = get_loggedin_userid(); - $attributes['container_guid'] = get_loggedin_userid(); - $attributes['site_guid'] = 0; + $attributes['subtype'] = NULL; + $attributes['owner_guid'] = elgg_get_logged_in_user_guid(); + $attributes['container_guid'] = elgg_get_logged_in_user_guid(); + $attributes['site_guid'] = NULL; $attributes['access_id'] = ACCESS_PRIVATE; - $attributes['time_created'] = ''; - $attributes['time_updated'] = ''; - $attributes['last_action'] = ''; + $attributes['time_created'] = NULL; + $attributes['time_updated'] = NULL; + $attributes['last_action'] = NULL; $attributes['enabled'] = 'yes'; $attributes['tables_split'] = 2; $attributes['tables_loaded'] = 0; - $attributes['title'] = ''; - $attributes['description'] = ''; + $attributes['title'] = NULL; + $attributes['description'] = NULL; ksort($attributes); $entity_attributes = $this->entity->expose_attributes(); @@ -87,11 +87,11 @@ class ElggCoreObjectTest extends ElggCoreUnitTest { public function testElggObjectLoad() { // fail on wrong type try { - $error = new ElggObjectTest(get_loggedin_userid()); + $error = new ElggObjectTest(elgg_get_logged_in_user_guid()); $this->assertTrue(FALSE); } catch (Exception $e) { $this->assertIsA($e, 'InvalidClassException'); - $message = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), get_loggedin_userid(), 'ElggObject'); + $message = sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), elgg_get_logged_in_user_guid(), 'ElggObject'); $this->assertIdentical($e->getMessage(), $message); } } @@ -144,18 +144,15 @@ class ElggCoreObjectTest extends ElggCoreUnitTest { } public function testElggObjectContainer() { - $this->assertEqual($this->entity->getContainer(), get_loggedin_userid()); - - // fals when container not a group - $this->assertFalse($this->entity->getContainerEntity()); + $this->assertEqual($this->entity->getContainerGUID(), elgg_get_logged_in_user_guid()); // create and save to group $group = new ElggGroup(); $guid = $group->save(); - $this->assertTrue($this->entity->setContainer($guid)); + $this->assertTrue($this->entity->setContainerGUID($guid)); // check container - $this->assertEqual($this->entity->getContainer(), $guid); + $this->assertEqual($this->entity->getContainerGUID(), $guid); $this->assertIdentical($group, $this->entity->getContainerEntity()); // clean up @@ -197,7 +194,99 @@ class ElggCoreObjectTest extends ElggCoreUnitTest { $old = elgg_set_ignore_access(true); } + // see https://github.com/elgg/elgg/issues/1196 + public function testElggEntityRecursiveDisableWhenLoggedOut() { + $e1 = new ElggObject(); + $e1->access_id = ACCESS_PUBLIC; + $e1->owner_guid = 0; + $e1->container_guid = 0; + $e1->save(); + $guid1 = $e1->getGUID(); + + $e2 = new ElggObject(); + $e2->container_guid = $guid1; + $e2->access_id = ACCESS_PUBLIC; + $e2->owner_guid = 0; + $e2->save(); + $guid2 = $e2->getGUID(); + + // fake being logged out + $user = $_SESSION['user']; + unset($_SESSION['user']); + $ia = elgg_set_ignore_access(true); + + $this->assertTrue(disable_entity($guid1, null, true)); + + // "log in" original user + $_SESSION['user'] = $user; + elgg_set_ignore_access($ia); + + $this->assertFalse(get_entity($guid1)); + $this->assertFalse(get_entity($guid2)); + + $db_prefix = get_config('dbprefix'); + $q = "SELECT * FROM {$db_prefix}entities WHERE guid = $guid1"; + $r = get_data_row($q); + $this->assertEqual('no', $r->enabled); + + $q = "SELECT * FROM {$db_prefix}entities WHERE guid = $guid2"; + $r = get_data_row($q); + $this->assertEqual('no', $r->enabled); + + access_show_hidden_entities(true); + delete_entity($guid1); + delete_entity($guid2); + access_show_hidden_entities(false); + } + + public function testElggRecursiveDelete() { + $types = array('ElggGroup', 'ElggObject', 'ElggUser', 'ElggSite'); + $db_prefix = elgg_get_config('dbprefix'); + + foreach ($types as $type) { + $parent = new $type(); + $this->assertTrue($parent->save()); + + $child = new ElggObject(); + $child->container_guid = $parent->guid; + $this->assertTrue($child->save()); + + $grandchild = new ElggObject(); + $grandchild->container_guid = $child->guid; + $this->assertTrue($grandchild->save()); + + $this->assertTrue($parent->delete(true)); + + $q = "SELECT * FROM {$db_prefix}entities WHERE guid = $parent->guid"; + $r = get_data($q); + $this->assertFalse($r); + $q = "SELECT * FROM {$db_prefix}entities WHERE guid = $child->guid"; + $r = get_data($q); + $this->assertFalse($r); + + $q = "SELECT * FROM {$db_prefix}entities WHERE guid = $grandchild->guid"; + $r = get_data($q); + $this->assertFalse($r); + } + + // object that owns itself + // can't check container_guid because of infinite loops in can_edit_entity() + $obj = new ElggObject(); + $obj->save(); + $obj->owner_guid = $obj->guid; + $obj->save(); + + $q = "SELECT * FROM {$db_prefix}entities WHERE guid = $obj->guid"; + $r = get_data_row($q); + $this->assertEqual($obj->guid, $r->owner_guid); + + $this->assertTrue($obj->delete(true)); + + $q = "SELECT * FROM {$db_prefix}entities WHERE guid = $obj->guid"; + $r = get_data_row($q); + $this->assertFalse($r); + } protected function get_object_row($guid) { global $CONFIG; diff --git a/engine/tests/objects/sites.php b/engine/tests/objects/sites.php index d8c458bc4..a01a661e3 100644 --- a/engine/tests/objects/sites.php +++ b/engine/tests/objects/sites.php @@ -18,7 +18,7 @@ class ElggCoreSiteTest extends ElggCoreUnitTest { * Called before each test method. */ public function setUp() { - $this->site = new ElggSiteTest; + $this->site = new ElggSiteTest(); } /** @@ -36,27 +36,24 @@ class ElggCoreSiteTest extends ElggCoreUnitTest { parent::__destruct(); } - /** - * A basic test that will be called and fail. - */ public function testElggSiteConstructor() { $attributes = array(); - $attributes['guid'] = ''; + $attributes['guid'] = NULL; $attributes['type'] = 'site'; - $attributes['subtype'] = ''; - $attributes['owner_guid'] = get_loggedin_userid(); - $attributes['container_guid'] = get_loggedin_userid(); - $attributes['site_guid'] = 0; + $attributes['subtype'] = NULL; + $attributes['owner_guid'] = elgg_get_logged_in_user_guid(); + $attributes['container_guid'] = elgg_get_logged_in_user_guid(); + $attributes['site_guid'] = NULL; $attributes['access_id'] = ACCESS_PRIVATE; - $attributes['time_created'] = ''; - $attributes['time_updated'] = ''; - $attributes['last_action'] = ''; + $attributes['time_created'] = NULL; + $attributes['time_updated'] = NULL; + $attributes['last_action'] = NULL; $attributes['enabled'] = 'yes'; $attributes['tables_split'] = 2; $attributes['tables_loaded'] = 0; - $attributes['name'] = ''; - $attributes['description'] = ''; - $attributes['url'] = ''; + $attributes['name'] = NULL; + $attributes['description'] = NULL; + $attributes['url'] = NULL; ksort($attributes); $entity_attributes = $this->site->expose_attributes(); @@ -66,8 +63,10 @@ class ElggCoreSiteTest extends ElggCoreUnitTest { } public function testElggSiteSaveAndDelete() { - $this->assertTrue($this->site->save()); - $this->assertTrue($this->site->delete()); + $guid = $this->site->save(); + $this->assertIsA($guid, 'int'); + $this->assertTrue($guid > 0); + $this->assertIdentical(true, $this->site->delete()); } } diff --git a/engine/tests/objects/users.php b/engine/tests/objects/users.php index fe5b48b03..8a1033ac4 100644 --- a/engine/tests/objects/users.php +++ b/engine/tests/objects/users.php @@ -41,33 +41,33 @@ class ElggCoreUserTest extends ElggCoreUnitTest { parent::__destruct(); } - /** - * A basic test that will be called and fail. - */ public function testElggUserConstructor() { $attributes = array(); - $attributes['guid'] = ''; + $attributes['guid'] = NULL; $attributes['type'] = 'user'; - $attributes['subtype'] = ''; - $attributes['owner_guid'] = get_loggedin_userid(); - $attributes['container_guid'] = get_loggedin_userid(); - $attributes['site_guid'] = 0; + $attributes['subtype'] = NULL; + $attributes['owner_guid'] = elgg_get_logged_in_user_guid(); + $attributes['container_guid'] = elgg_get_logged_in_user_guid(); + $attributes['site_guid'] = NULL; $attributes['access_id'] = ACCESS_PRIVATE; - $attributes['time_created'] = ''; - $attributes['time_updated'] = ''; - $attributes['last_action'] = ''; + $attributes['time_created'] = NULL; + $attributes['time_updated'] = NULL; + $attributes['last_action'] = NULL; $attributes['enabled'] = 'yes'; $attributes['tables_split'] = 2; $attributes['tables_loaded'] = 0; - $attributes['name'] = ''; - $attributes['username'] = ''; - $attributes['password'] = ''; - $attributes['salt'] = ''; - $attributes['email'] = ''; - $attributes['language'] = ''; - $attributes['code'] = ''; + $attributes['name'] = NULL; + $attributes['username'] = NULL; + $attributes['password'] = NULL; + $attributes['salt'] = NULL; + $attributes['email'] = NULL; + $attributes['language'] = NULL; + $attributes['code'] = NULL; $attributes['banned'] = 'no'; $attributes['admin'] = 'no'; + $attributes['prev_last_action'] = NULL; + $attributes['last_login'] = NULL; + $attributes['prev_last_login'] = NULL; ksort($attributes); $entity_attributes = $this->user->expose_attributes(); @@ -98,7 +98,7 @@ class ElggCoreUserTest extends ElggCoreUnitTest { } public function testElggUserConstructorByGuid() { - $user = new ElggUser(get_loggedin_userid()); + $user = new ElggUser(elgg_get_logged_in_user_guid()); $this->assertIdentical($user, $_SESSION['user']); // fail with garbage @@ -113,13 +113,13 @@ class ElggCoreUserTest extends ElggCoreUnitTest { } public function testElggUserConstructorByDbRow() { - $row = $this->fetchUser(get_loggedin_userid()); + $row = $this->fetchUser(elgg_get_logged_in_user_guid()); $user = new ElggUser($row); $this->assertIdentical($user, $_SESSION['user']); } public function testElggUserConstructorByUsername() { - $row = $this->fetchUser(get_loggedin_userid()); + $row = $this->fetchUser(elgg_get_logged_in_user_guid()); $user = new ElggUser($row->username); $this->assertIdentical($user, $_SESSION['user']); } @@ -138,14 +138,14 @@ class ElggCoreUserTest extends ElggCoreUnitTest { $guid = $this->user->save(); // delete object - $this->assertTrue($this->user->delete()); + $this->assertIdentical(true, $this->user->delete()); // check GUID not in database $this->assertFalse($this->fetchUser($guid)); } public function testElggUserNameCache() { - // Trac #1305 + // issue https://github.com/elgg/elgg/issues/1305 // very unlikely a user would have this username $name = (string)time(); @@ -159,6 +159,22 @@ class ElggCoreUserTest extends ElggCoreUnitTest { $this->assertFalse($user); } + public function testGetUserByUsernameAcceptsUrlEncoded() { + $username = (string)time(); + $this->user->username = $username; + $guid = $this->user->save(); + + // percent encode first letter + $first_letter = $username[0]; + $first_letter = str_pad('%' . dechex(ord($first_letter)), 2, '0', STR_PAD_LEFT); + $username = $first_letter . substr($username, 1); + + $user = get_user_by_username($username); + $this->assertTrue((bool) $user); + $this->assertEqual($guid, $user->guid); + + $this->user->delete(); + } public function testElggUserMakeAdmin() { global $CONFIG; @@ -220,29 +236,6 @@ class ElggCoreUserTest extends ElggCoreUnitTest { $this->user->delete(); } - // remove in 1.9 - public function testElggUserIsAdminLegacy() { - $this->user->save(); - $this->user->makeAdmin(); - - $this->assertTrue($this->user->admin); - $this->assertTrue($this->user->siteadmin); - - $this->user->removeAdmin(); - $this->user->delete(); - } - - public function testElggUserIsNotAdminLegacy() { - $this->user->save(); - $this->user->removeAdmin(); - - $this->assertFalse($this->user->admin); - $this->assertFalse($this->user->siteadmin); - - $this->user->removeAdmin(); - $this->user->delete(); - } - protected function fetchUser($guid) { global $CONFIG; diff --git a/engine/tests/regression/trac_bugs.php b/engine/tests/regression/trac_bugs.php index 93831fd7e..689275661 100644 --- a/engine/tests/regression/trac_bugs.php +++ b/engine/tests/regression/trac_bugs.php @@ -1,7 +1,7 @@ <?php /** - * Elgg Regression Tests -- Trac Bugfixes - * Any bugfixes from Trac that require testing belong here. + * Elgg Regression Tests -- GitHub Bugfixes + * Any bugfixes from GitHub that require testing belong here. * * @package Elgg * @subpackage Test @@ -14,7 +14,7 @@ class ElggCoreRegressionBugsTest extends ElggCoreUnitTest { public function __construct() { $this->ia = elgg_set_ignore_access(TRUE); parent::__construct(); - + // all __construct() code should come after here } @@ -45,23 +45,23 @@ class ElggCoreRegressionBugsTest extends ElggCoreUnitTest { /** * #1558 */ - public function testElggObjectClearAnnotations() { + public function testElggObjectDeleteAnnotations() { $this->entity = new ElggObject(); $guid = $this->entity->save(); - + $this->entity->annotate('test', 'hello', ACCESS_PUBLIC); - - $this->entity->clearAnnotations('does not exist'); - + + $this->entity->deleteAnnotations('does not exist'); + $num = $this->entity->countAnnotations('test'); - + //$this->assertIdentical($num, 1); $this->assertEqual($num, 1); - + // clean up $this->entity->delete(); } - + /** * #2063 - get_resized_image_from_existing_file() fails asked for image larger than selection and not scaling an image up * Test get_image_resize_parameters(). @@ -69,47 +69,337 @@ class ElggCoreRegressionBugsTest extends ElggCoreUnitTest { public function testElggResizeImage() { $orig_width = 100; $orig_height = 150; - + // test against selection > max $options = array( 'maxwidth' => 50, 'maxheight' => 50, 'square' => TRUE, 'upscale' => FALSE, - + 'x1' => 25, 'y1' => 75, 'x2' => 100, 'y2' => 150 ); - - // should get back the same x/y offset == x1, y1 and an image of 50x50 + + // should get back the same x/y offset == x1, y1 and an image of 50x50 $params = get_image_resize_parameters($orig_width, $orig_height, $options); - + $this->assertEqual($params['newwidth'], $options['maxwidth']); $this->assertEqual($params['newheight'], $options['maxheight']); $this->assertEqual($params['xoffset'], $options['x1']); $this->assertEqual($params['yoffset'], $options['y1']); - + // test against selection < max $options = array( 'maxwidth' => 50, 'maxheight' => 50, 'square' => TRUE, 'upscale' => FALSE, - + 'x1' => 75, 'y1' => 125, 'x2' => 100, 'y2' => 150 ); - // should get back the same x/y offset == x1, y1 and an image of 25x25 + // should get back the same x/y offset == x1, y1 and an image of 25x25 because no upscale $params = get_image_resize_parameters($orig_width, $orig_height, $options); - + $this->assertEqual($params['newwidth'], 25); $this->assertEqual($params['newheight'], 25); $this->assertEqual($params['xoffset'], $options['x1']); $this->assertEqual($params['yoffset'], $options['y1']); } + + // #3722 Check canEdit() works for contains regardless of groups + function test_can_write_to_container() { + $user = new ElggUser(); + $user->username = 'test_user_' . rand(); + $user->name = 'test_user_name_' . rand(); + $user->email = 'test@user.net'; + $user->container_guid = 0; + $user->owner_guid = 0; + $user->save(); + + $object = new ElggObject(); + $object->save(); + + $group = new ElggGroup(); + $group->save(); + + // disable access overrides because we're admin. + $ia = elgg_set_ignore_access(false); + + $this->assertFalse(can_write_to_container($user->guid, $object->guid)); + + global $elgg_test_user; + $elgg_test_user = $user; + + // register hook to allow access + function can_write_to_container_test_hook($hook, $type, $value, $params) { + global $elgg_test_user; + + if ($params['user']->getGUID() == $elgg_test_user->getGUID()) { + return true; + } + } + + elgg_register_plugin_hook_handler('container_permissions_check', 'all', 'can_write_to_container_test_hook'); + $this->assertTrue(can_write_to_container($user->guid, $object->guid)); + elgg_unregister_plugin_hook_handler('container_permissions_check', 'all', 'can_write_to_container_test_hook'); + + $this->assertFalse(can_write_to_container($user->guid, $group->guid)); + $group->join($user); + $this->assertTrue(can_write_to_container($user->guid, $group->guid)); + + elgg_set_ignore_access($ia); + + $user->delete(); + $object->delete(); + $group->delete(); + } + + function test_db_shutdown_links() { + global $DB_DELAYED_QUERIES, $test_results; + $DB_DELAYED_QUERIES = array(); + + function test_delayed_results($results) { + global $test_results; + $test_results = $results; + } + + $q = 'SELECT 1 as test'; + + $links = array('read', 'write', get_db_link('read'), get_db_link('write')); + + foreach ($links as $link) { + $DB_DELAYED_QUERIES = array(); + + $result = execute_delayed_query($q, $link, 'test_delayed_results'); + + $this->assertTrue($result, "Failed with link = $link"); + $this->assertEqual(count($DB_DELAYED_QUERIES), 1); + $this->assertEqual($DB_DELAYED_QUERIES[0]['q'], $q); + $this->assertEqual($DB_DELAYED_QUERIES[0]['l'], $link); + $this->assertEqual($DB_DELAYED_QUERIES[0]['h'], 'test_delayed_results'); + + db_delayedexecution_shutdown_hook(); + + $num_rows = mysql_num_rows($test_results); + $this->assertEqual($num_rows, 1); + $row = mysql_fetch_assoc($test_results); + $this->assertEqual($row['test'], 1); + } + + // test bad case + $DB_DELAYED_QUERIES = array(); + $result = execute_delayed_query($q, 'not_a_link', 'test_delayed_results'); + $this->assertFalse($result); + $this->assertEqual(array(), $DB_DELAYED_QUERIES); + } + + /** + * https://github.com/elgg/elgg/issues/3210 - Don't remove -s in friendly titles + * https://github.com/elgg/elgg/issues/2276 - improve char encoding + */ + public function test_friendly_title() { + $cases = array( + // acid test + "B&N > Amazon, OK? <bold> 'hey!' $34" + => "bn-amazon-ok-bold-hey-34", + + // hyphen, underscore and ASCII whitespace replaced by separator, + // other non-alphanumeric ASCII removed + "a-a_a a\na\ra\ta\va!a\"a#a\$a%aa'a(a)a*a+a,a.a/a:a;a=a?a@a[a\\a]a^a`a{a|a}a~a" + => "a-a-a-a-a-a-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + + // separators trimmed + "-_ hello _-" + => "hello", + + // accents removed, lower case, other multibyte chars are URL encoded + "I\xC3\xB1t\xC3\xABrn\xC3\xA2ti\xC3\xB4n\xC3\xA0liz\xC3\xA6ti\xC3\xB8n, AND \xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" + // Iñtërnâtiônàlizætiøn, AND 日本語 + => 'internationalizaetion-and-%E6%97%A5%E6%9C%AC%E8%AA%9E', + ); + + // where available, string is converted to NFC before transliteration + if (ElggTranslit::hasNormalizerSupport()) { + $form_d = "A\xCC\x8A"; // A followed by 'COMBINING RING ABOVE' (U+030A) + $cases[$form_d] = "a"; + } + + foreach ($cases as $case => $expected) { + $friendly_title = elgg_get_friendly_title($case); + $this->assertIdentical($expected, $friendly_title); + } + } + + /** + * Test #5369 -- parse_urls() + * https://github.com/Elgg/Elgg/issues/5369 + */ + public function test_parse_urls() { + + $cases = array( + 'no.link.here' => + 'no.link.here', + 'simple link http://example.org test' => + 'simple link <a href="http://example.org" rel="nofollow">http:/<wbr />/<wbr />example.org</a> test', + 'non-ascii http://ñew.org/ test' => + 'non-ascii <a href="http://ñew.org/" rel="nofollow">http:/<wbr />/<wbr />ñew.org/<wbr /></a> test', + + // section 2.1 + 'percent encoded http://example.org/a%20b test' => + 'percent encoded <a href="http://example.org/a%20b" rel="nofollow">http:/<wbr />/<wbr />example.org/<wbr />a%20b</a> test', + // section 2.2: skipping single quote and parenthese + 'reserved characters http://example.org/:/?#[]@!$&*+,;= test' => + 'reserved characters <a href="http://example.org/:/?#[]@!$&*+,;=" rel="nofollow">http:/<wbr />/<wbr />example.org/<wbr />:/<wbr />?#[]@!$&*+,;=</a> test', + // section 2.3 + 'unreserved characters http://example.org/a1-._~ test' => + 'unreserved characters <a href="http://example.org/a1-._~" rel="nofollow">http:/<wbr />/<wbr />example.org/<wbr />a1-._~</a> test', + + 'parameters http://example.org/?val[]=1&val[]=2 test' => + 'parameters <a href="http://example.org/?val[]=1&val[]=2" rel="nofollow">http:/<wbr />/<wbr />example.org/<wbr />?val[]=1&val[]=2</a> test', + 'port http://example.org:80/ test' => + 'port <a href="http://example.org:80/" rel="nofollow">http:/<wbr />/<wbr />example.org:80/<wbr /></a> test', + + 'parentheses (http://www.google.com) test' => + 'parentheses (<a href="http://www.google.com" rel="nofollow">http:/<wbr />/<wbr />www.google.com</a>) test', + 'comma http://elgg.org, test' => + 'comma <a href="http://elgg.org" rel="nofollow">http:/<wbr />/<wbr />elgg.org</a>, test', + 'period http://elgg.org. test' => + 'period <a href="http://elgg.org" rel="nofollow">http:/<wbr />/<wbr />elgg.org</a>. test', + 'exclamation http://elgg.org! test' => + 'exclamation <a href="http://elgg.org" rel="nofollow">http:/<wbr />/<wbr />elgg.org</a>! test', + + 'already anchor <a href="http://twitter.com/">twitter</a> test' => + 'already anchor <a href="http://twitter.com/">twitter</a> test', + + 'ssl https://example.org/ test' => + 'ssl <a href="https://example.org/" rel="nofollow">https:/<wbr />/<wbr />example.org/<wbr /></a> test', + 'ftp ftp://example.org/ test' => + 'ftp <a href="ftp://example.org/" rel="nofollow">ftp:/<wbr />/<wbr />example.org/<wbr /></a> test', + + 'web archive anchor <a href="http://web.archive.org/web/20000229040250/http://www.google.com/">google</a>' => + 'web archive anchor <a href="http://web.archive.org/web/20000229040250/http://www.google.com/">google</a>', + + 'single quotes already anchor <a href=\'http://www.yahoo.com\'>yahoo</a>' => + 'single quotes already anchor <a href=\'http://www.yahoo.com\'>yahoo</a>', + + 'unquoted already anchor <a href=http://www.yahoo.com>yahoo</a>' => + 'unquoted already anchor <a href=http://www.yahoo.com>yahoo</a>', + + 'parens in uri http://thedailywtf.com/Articles/A-(Long-Overdue)-BuildMaster-Introduction.aspx' => + 'parens in uri <a href="http://thedailywtf.com/Articles/A-(Long-Overdue)-BuildMaster-Introduction.aspx" rel="nofollow">http:/<wbr />/<wbr />thedailywtf.com/<wbr />Articles/<wbr />A-(Long-Overdue)-BuildMaster-Introduction.aspx</a>' + ); + foreach ($cases as $input => $output) { + $this->assertEqual($output, parse_urls($input)); + } + } + + /** + * Ensure additional select columns do not end up in entity attributes. + * + * https://github.com/Elgg/Elgg/issues/5538 + */ + public function test_extra_columns_dont_appear_in_attributes() { + global $ENTITY_CACHE; + + // may not have groups in DB - let's create one + $group = new ElggGroup(); + $group->name = 'test_group'; + $group->access_id = ACCESS_PUBLIC; + $this->assertTrue($group->save() !== false); + + // entity cache interferes with our test + $ENTITY_CACHE = array(); + + foreach (array('site', 'user', 'group', 'object') as $type) { + $entities = elgg_get_entities(array( + 'type' => $type, + 'selects' => array('1 as _nonexistent_test_column'), + 'limit' => 1, + )); + if (!$this->assertTrue($entities, "Query for '$type' did not return an entity.")) { + continue; + } + $entity = $entities[0]; + $this->assertNull($entity->_nonexistent_test_column, "Additional select columns are leaking to attributes for '$type'"); + } + + $group->delete(); + } + + /** + * Ensure that ElggBatch doesn't go into infinite loop when disabling annotations recursively when show hidden is enabled. + * + * https://github.com/Elgg/Elgg/issues/5952 + */ + public function test_disabling_annotations_infinite_loop() { + + //let's have some entity + $group = new ElggGroup(); + $group->name = 'test_group'; + $group->access_id = ACCESS_PUBLIC; + $this->assertTrue($group->save() !== false); + + $total = 51; + //add some annotations + for ($cnt = 0; $cnt < $total; $cnt++) { + $group->annotate('test_annotation', 'value_' . $total); + } + + //disable them + $show_hidden = access_get_show_hidden_status(); + access_show_hidden_entities(true); + $options = array( + 'guid' => $group->guid, + 'limit' => $total, //using strict limit to avoid real infinite loop and just see ElggBatch limiting on it before finishing the work + ); + elgg_disable_annotations($options); + access_show_hidden_entities($show_hidden); + + //confirm all being disabled + $annotations = $group->getAnnotations(array( + 'limit' => $total, + )); + foreach ($annotations as $annotation) { + $this->assertTrue($annotation->enabled == 'no'); + } + + //delete group and annotations + $group->delete(); + } + + public function test_ElggXMLElement_does_not_load_external_entities() { + $elLast = libxml_disable_entity_loader(false); + + // build payload that should trigger loading of external entity + $payload = file_get_contents(dirname(dirname(__FILE__)) . '/test_files/xxe/request.xml'); + $path = realpath(dirname(dirname(__FILE__)) . '/test_files/xxe/external_entity.txt'); + $path = str_replace('\\', '/', $path); + if ($path[0] != '/') { + $path = '/' . $path; + } + $path = 'file://' . $path; + $payload = sprintf($payload, $path); + + // make sure we can actually this in this environment + $element = new SimpleXMLElement($payload); + $can_load_entity = preg_match('/secret/', (string)$element->methodName); + + $this->skipUnless($can_load_entity, "XXE vulnerability cannot be tested on this system"); + + if ($can_load_entity) { + $el = new ElggXMLElement($payload); + $chidren = $el->getChildren(); + $content = $chidren[0]->getContent(); + $this->assertNoPattern('/secret/', $content); + } + + libxml_disable_entity_loader($elLast); + } } diff --git a/engine/tests/services/api.php b/engine/tests/services/api.php index 57396c3a0..3d07c0bbb 100644 --- a/engine/tests/services/api.php +++ b/engine/tests/services/api.php @@ -19,7 +19,7 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { // expose_function public function testExposeFunctionNoMethod() { try { - expose_function(); + @expose_function(); $this->assertTrue(FALSE); } catch (Exception $e) { $this->assertIsA($e, 'InvalidParameterException'); @@ -29,7 +29,7 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { public function testExposeFunctionNoFunction() { try { - expose_function('test'); + @expose_function('test'); $this->assertTrue(FALSE); } catch (Exception $e) { $this->assertIsA($e, 'InvalidParameterException'); @@ -39,7 +39,7 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { public function testExposeFunctionBadParameters() { try { - expose_function('test', 'test', 'BAD'); + @expose_function('test', 'test', 'BAD'); $this->assertTrue(FALSE); } catch (Exception $e) { $this->assertIsA($e, 'InvalidParameterException'); @@ -59,7 +59,7 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { public function testExposeFunctionBadHttpMethod() { try { - expose_function('test', 'test', null, '', 'BAD'); + @expose_function('test', 'test', null, '', 'BAD'); $this->assertTrue(FALSE); } catch (Exception $e) { $this->assertIsA($e, 'InvalidParameterException'); @@ -128,7 +128,6 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { $this->assertTrue(FALSE); } catch (Exception $e) { $this->assertIsA($e, 'APIException'); - $this->assertIdentical($e->getMessage(), elgg_echo('APIException:UserAuthenticationFailed')); } } @@ -284,9 +283,9 @@ class ElggCoreServicesApiTest extends ElggCoreUnitTest { } // api key methods - public function testApiAuthenticate() { - $this->assertFalse(pam_authenticate(null, "api")); - } + //public function testApiAuthenticate() { + // $this->assertFalse(pam_authenticate(null, "api")); + //} public function testApiAuthKeyNoKey() { try { diff --git a/engine/tests/suite.php b/engine/tests/suite.php index 23637b9b1..4203bc5d6 100644 --- a/engine/tests/suite.php +++ b/engine/tests/suite.php @@ -9,6 +9,8 @@ require_once(dirname( __FILE__ ) . '/../start.php'); +admin_gatekeeper(); + $vendor_path = "$CONFIG->path/vendors/simpletest"; $test_path = "$CONFIG->path/engine/tests"; @@ -18,8 +20,8 @@ require_once("$vendor_path/reporter.php"); require_once("$test_path/elgg_unit_test.php"); // turn off system log -unregister_elgg_event_handler('all', 'all', 'system_log_listener'); -unregister_elgg_event_handler('log', 'systemlog', 'system_log_default_logger'); +elgg_unregister_event_handler('all', 'all', 'system_log_listener'); +elgg_unregister_event_handler('log', 'systemlog', 'system_log_default_logger'); // Disable maximum execution time. // Tests take a while... @@ -28,7 +30,7 @@ set_time_limit(0); $suite = new TestSuite('Elgg Core Unit Tests'); // emit a hook to pull in all tests -$test_files = trigger_plugin_hook('unit_test', 'system', null, array()); +$test_files = elgg_trigger_plugin_hook('unit_test', 'system', null, array()); foreach ($test_files as $file) { $suite->addTestFile($file); } @@ -47,5 +49,5 @@ if (TextReporter::inCli()) { // Ensure that only logged-in users can see this page //admin_gatekeeper(); $old = elgg_set_ignore_access(TRUE); -$suite->Run(new HtmlReporter()); +$suite->Run(new HtmlReporter('utf-8')); elgg_set_ignore_access($old); diff --git a/engine/tests/test_files/output/autop/block-a.exp.norun.html b/engine/tests/test_files/output/autop/block-a.exp.norun.html new file mode 100644 index 000000000..addf29dec --- /dev/null +++ b/engine/tests/test_files/output/autop/block-a.exp.norun.html @@ -0,0 +1,6 @@ + +<p>HTML5 allows A to contain block-level content</p> +<a href="foo"><h3>A treated as block</h3> +<p>Read more</p> +</a> +<p><a href="foo">A treated as<br /> inline</a></p> diff --git a/engine/tests/test_files/output/autop/block-a.in.norun.html b/engine/tests/test_files/output/autop/block-a.in.norun.html new file mode 100644 index 000000000..fc2dac43a --- /dev/null +++ b/engine/tests/test_files/output/autop/block-a.in.norun.html @@ -0,0 +1,9 @@ +HTML5 allows A to contain block-level content +<a href="foo"> + + <h3>A treated as block</h3> + + Read more +</a> +<a href="foo">A treated as + inline</a> diff --git a/engine/tests/test_files/output/autop/domdoc_exp.html b/engine/tests/test_files/output/autop/domdoc_exp.html new file mode 100644 index 000000000..8480c1083 --- /dev/null +++ b/engine/tests/test_files/output/autop/domdoc_exp.html @@ -0,0 +1,46 @@ +› + +Vietnamese - Tiếng Việt + +<h1>h1</h1> +<p>Paragraph <a href="http://google.com/">link</a> <strong>Bold</strong> <em>italic</em> <em><strong>bolditalic</strong></em> <span style="background-color: #ffff00; "></span></p> +<h2>h2</h2> +<p>Paragraph <span style="font-size: xx-small;">size1</span> <span style="font-size: x-small;">size2</span> <span style="font-size: medium;">size4</span></p> +<h3>h3</h3> +<p>Paragraph <span style="text-decoration: underline;">underline</span> <span style="text-decoration: line-through;">strikethrough</span> <span style="color: #ff0000;">color</span> <span style="background-color: #ffff00; ">background</span></p> +<blockquote> + <p>Blockquoted paragraph</p> +</blockquote> +<p>Paragraph following blockquote</p> +<ul><li>Unordered</li> + <li>List</li> +</ul><p>Paragraph between lists</p> +<ol><li>Ordered</li> + <li>List</li> +</ol><p>Paragraph between lists</p> +<ul><li>OL list</li> + <li>nested<ol><li>inside a</li> + <li>UL list</li> + </ol></li> +</ul><p>Paragraph between lists</p> +<table border="0"><tbody><tr><td>Table with</td> + <td></td> + </tr><tr><td></td> + <td>border=0</td> + </tr></tbody></table><p>Paragraph</p> +<ol><li>UL list</li> + <li>nested + <ul><li>inside a</li> + <li>OL list</li> + </ul></li> +</ol><p>Paragraph between tables</p> +<table border="1" cellpadding="5"><tbody><tr><td>Table with border=1</td> + <td></td> + </tr><tr><td></td> + <td>cellpadding = 5</td> + </tr></tbody></table><p>Paragraph between tables</p> +<table border="2"><tbody><tr><td>Table with</td> + <td></td> + </tr><tr><td></td> + <td>border=2</td> + </tr></tbody></table>
\ No newline at end of file diff --git a/engine/tests/test_files/output/autop/domdoc_in.html b/engine/tests/test_files/output/autop/domdoc_in.html new file mode 100644 index 000000000..4c465b435 --- /dev/null +++ b/engine/tests/test_files/output/autop/domdoc_in.html @@ -0,0 +1,80 @@ +› + +Vietnamese - Tiếng Việt + +<h1>h1</h1> +<p>Paragraph <a href="http://google.com/">link</a> <strong>Bold</strong> <em>italic</em> <em><strong>bolditalic</strong></em> <span style="background-color: #ffff00; "></span></p> +<h2>h2</h2> +<p>Paragraph <span style="font-size: xx-small;">size1</span> <span style="font-size: x-small;">size2</span> <span style="font-size: medium;">size4</span></p> +<h3>h3</h3> +<p>Paragraph <span style="text-decoration: underline;">underline</span> <span style="text-decoration: line-through;">strikethrough</span> <span style="color: #ff0000;">color</span> <span style="background-color: #ffff00; ">background</span></p> +<blockquote> + <p>Blockquoted paragraph</p> +</blockquote> +<p>Paragraph following blockquote</p> +<ul> + <li>Unordered</li> + <li>List</li> +</ul> +<p>Paragraph between lists</p> +<ol> + <li>Ordered</li> + <li>List</li> +</ol> +<p>Paragraph between lists</p> +<ul> + <li>OL list</li> + <li>nested<ol> + <li>inside a</li> + <li>UL list</li> + </ol></li> +</ul> +<p>Paragraph between lists</p> +<table border="0"> + <tbody> + <tr> + <td>Table with</td> + <td></td> + </tr> + <tr> + <td></td> + <td>border=0</td> + </tr> + </tbody> +</table> +<p>Paragraph</p> +<ol> + <li>UL list</li> + <li>nested + <ul> + <li>inside a</li> + <li>OL list</li> + </ul> + </li> +</ol> +<p>Paragraph between tables</p> +<table border="1" cellpadding="5"> + <tbody> + <tr> + <td>Table with border=1</td> + <td></td> + </tr> + <tr> + <td></td> + <td>cellpadding = 5</td> + </tr> + </tbody> +</table> +<p>Paragraph between tables</p> +<table border="2"> + <tbody> + <tr> + <td>Table with</td> + <td></td> + </tr> + <tr> + <td></td> + <td>border=2</td> + </tr> + </tbody> +</table>
\ No newline at end of file diff --git a/engine/tests/test_files/output/autop/typical-post.exp.html b/engine/tests/test_files/output/autop/typical-post.exp.html new file mode 100644 index 000000000..f9d75a114 --- /dev/null +++ b/engine/tests/test_files/output/autop/typical-post.exp.html @@ -0,0 +1,84 @@ +<h2>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h2> +<p><img class="alignright size-thumbnail wp-image-905" title="Surest Things mixing session in Adobe Audition" src="http://www.mrclay.org/wp-content/uploads/2010/09/surestThings_audition-150x150.png" alt="screenshot of Audition mixing session" width="150" height="150">Vivamus enim ante, <em>mattis eget imperdiet nec, pharetra vel velit.</em> Sed at euismod nibh. Praesent lacus tellus, <a href="http://google.com/">posuere et convallis</a> a, <strong>mollis et tellus. Suspendisse potenti</strong>. Phasellus tincidunt dignissim est eget mattis. Vestibulum lacinia <del>condimentum tellus, non vestibulum erat dapibus</del> quis. Aliquam arcu nibh, viverra adipiscing eleifend quis, pretium vitae ipsum.</p> + +<p>Curabitur turpis ante, <span style="color: #993300;">congue ac dapibus quis, vehicula ac orci.</span> Nunc luctus neque non massa porta sed pharetra ante accumsan. <a href="http://google.com/">Nam suscipit</a> risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor.</p> +<h3>Donec at massa ante, sagittis fermentum urna.</h3><blockquote> +<p>Mauris volutpat est id massa volutpat lacinia. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In in nisl mauris. In aliquet pretium nisl, vel convallis neque cursus vitae. Curabitur id mauris in urna gravida ornare.</p> + +<p>[caption id="attachment_719" align="alignleft" width="150" caption="Ibanez AGB140 Bass"]<img class="size-thumbnail wp-image-719" title="Ibanez AGB140 Bass" src="http://www.mrclay.org/wp-content/uploads/2010/04/agb140-e1271773766573-150x150.jpg" alt="Ibanez AGB140 Bass" width="150" height="150">[/caption]</p> + +<p>Aenean <a href="http://google.com/">aliquet cursus purus sed gravida. Cras auctor euismod justo, ac dictum purus facilisis dignissim.</a> Quisque facilisis porta sem, ac suscipit quam molestie nec. Pellentesque quis hendrerit enim. Vivamus tempor erat diam. Sed eu felis nunc. Cras posuere lorem commodo turpis mollis sagittis. Mauris lobortis nunc felis.</p> + +<p>Maecenas elit lorem, varius sed condimentum ac, cursus et magna. Nam ut massa id augue consectetur porttitor eleifend in nunc. Curabitur cursus varius dictum. Vestibulum vel justo et neque tempus placerat a vel sapien.</p> +</blockquote> +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus enim ante, mattis eget imperdiet nec, <a href="http://google.com/">pharetra </a>vel velit. Sed at euismod nibh. Praesent lacus tellus, posuere et convallis a, mollis et tellus.</p> +<pre><code><?php +class DataTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provider + */ + public function testAdd($a, $b, $c) + { + $this->assertEquals($c, $a + $b); + } + + public function provider() + { + return array( + array(0, 0, 0), + array(0, 1, 1), + array(1, 0, 1), + array(1, 1, 3) + ); + } +}</code></pre><ul><li>Suspendisse potenti. Phasellus tincidunt dignissim est eget mattis.</li> + <li>Vestibulum lacinia condimentum tellus, non vestibulum erat dapibus quis.</li> + <li>Aliquam arcu nibh, <a href="http://google.com/">viverra</a> adipiscing eleifend quis, pretium vitae ipsum.</li> + <li>Curabitur turpis ante, congue ac <a href="http://google.com/">dapibus quis</a>, vehicula ac orci.</li> +</ul> +<p>Nunc luctus neque non massa porta sed pharetra ante accumsan. Nam suscipit risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor. Donec at massa ante, sagittis fermentum urna.</p> + +<p><object width="480" height="390"><param name="movie" value="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&hl=en_US"><param name="allowFullScreen" value="true"><param name="allowscriptaccess" value="always"><embed type="application/x-shockwave-flash" width="480" height="390" src="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&hl=en_US" allowfullscreen="true" allowscriptaccess="always"></embed></object></p> +<h2>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h2> +<p><img class="alignright size-thumbnail wp-image-905" title="Surest Things mixing session in Adobe Audition" src="http://www.mrclay.org/wp-content/uploads/2010/09/surestThings_audition-150x150.png" alt="screenshot of Audition mixing session" width="150" height="150">Vivamus enim ante, <em>mattis eget imperdiet nec, pharetra vel velit.</em> Sed at euismod nibh. Praesent lacus tellus, <a href="http://google.com/">posuere et convallis</a> a, <strong>mollis et tellus. Suspendisse potenti</strong>. Phasellus tincidunt dignissim est eget mattis. Vestibulum lacinia <del>condimentum tellus, non vestibulum erat dapibus</del> quis. Aliquam arcu nibh, viverra adipiscing eleifend quis, pretium vitae ipsum.</p> + +<p>Curabitur turpis ante, <span style="color: #993300;">congue ac dapibus quis, vehicula ac orci.</span> Nunc luctus neque non massa porta sed pharetra ante accumsan. <a href="http://google.com/">Nam suscipit</a> risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor.</p> +<h3>Donec at massa ante, sagittis fermentum urna.</h3><blockquote> +<p>Mauris volutpat est id massa volutpat lacinia. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In in nisl mauris. In aliquet pretium nisl, vel convallis neque cursus vitae. Curabitur id mauris in urna gravida ornare.</p> + +<p>[caption id="attachment_719" align="alignleft" width="150" caption="Ibanez AGB140 Bass"]<img class="size-thumbnail wp-image-719" title="Ibanez AGB140 Bass" src="http://www.mrclay.org/wp-content/uploads/2010/04/agb140-e1271773766573-150x150.jpg" alt="Ibanez AGB140 Bass" width="150" height="150">[/caption]</p> + +<p>Aenean <a href="http://google.com/">aliquet cursus purus sed gravida. Cras auctor euismod justo, ac dictum purus facilisis dignissim.</a> Quisque facilisis porta sem, ac suscipit quam molestie nec. Pellentesque quis hendrerit enim. Vivamus tempor erat diam. Sed eu felis nunc. Cras posuere lorem commodo turpis mollis sagittis. Mauris lobortis nunc felis.</p> + +<p>Maecenas elit lorem, varius sed condimentum ac, cursus et magna. Nam ut massa id augue consectetur porttitor eleifend in nunc. Curabitur cursus varius dictum. Vestibulum vel justo et neque tempus placerat a vel sapien.</p> +</blockquote> +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus enim ante, mattis eget imperdiet nec, <a href="http://google.com/">pharetra </a>vel velit. Sed at euismod nibh. Praesent lacus tellus, posuere et convallis a, mollis et tellus.</p> +<pre><code><?php +class DataTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provider + */ + public function testAdd($a, $b, $c) + { + $this->assertEquals($c, $a + $b); + } + + public function provider() + { + return array( + array(0, 0, 0), + array(0, 1, 1), + array(1, 0, 1), + array(1, 1, 3) + ); + } +}</code></pre><ul><li>Suspendisse potenti. Phasellus tincidunt dignissim est eget mattis.</li> + <li>Vestibulum lacinia condimentum tellus, non vestibulum erat dapibus quis.</li> + <li>Aliquam arcu nibh, <a href="http://google.com/">viverra</a> adipiscing eleifend quis, pretium vitae ipsum.</li> + <li>Curabitur turpis ante, congue ac <a href="http://google.com/">dapibus quis</a>, vehicula ac orci.</li> +</ul> +<p>Nunc luctus neque non massa porta sed pharetra ante accumsan. Nam suscipit risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor. Donec at massa ante, sagittis fermentum urna.</p> + +<p><object width="480" height="390"><param name="movie" value="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&hl=en_US"><param name="allowFullScreen" value="true"><param name="allowscriptaccess" value="always"><embed type="application/x-shockwave-flash" width="480" height="390" src="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&hl=en_US" allowfullscreen="true" allowscriptaccess="always"></embed></object></p> diff --git a/engine/tests/test_files/output/autop/typical-post.in.html b/engine/tests/test_files/output/autop/typical-post.in.html new file mode 100644 index 000000000..6e4984cc4 --- /dev/null +++ b/engine/tests/test_files/output/autop/typical-post.in.html @@ -0,0 +1,89 @@ +<h2>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h2> +<img class="alignright size-thumbnail wp-image-905" title="Surest Things mixing session in Adobe Audition" src="http://www.mrclay.org/wp-content/uploads/2010/09/surestThings_audition-150x150.png" alt="screenshot of Audition mixing session" width="150" height="150" />Vivamus enim ante, <em>mattis eget imperdiet nec, pharetra vel velit.</em> Sed at euismod nibh. Praesent lacus tellus, <a href="http://google.com/">posuere et convallis</a> a, <strong>mollis et tellus. Suspendisse potenti</strong>. Phasellus tincidunt dignissim est eget mattis. Vestibulum lacinia <del>condimentum tellus, non vestibulum erat dapibus</del> quis. Aliquam arcu nibh, viverra adipiscing eleifend quis, pretium vitae ipsum. + +Curabitur turpis ante, <span style="color: #993300;">congue ac dapibus quis, vehicula ac orci.</span> Nunc luctus neque non massa porta sed pharetra ante accumsan. <a href="http://google.com/">Nam suscipit</a> risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor. +<h3>Donec at massa ante, sagittis fermentum urna.</h3> +<blockquote>Mauris volutpat est id massa volutpat lacinia. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In in nisl mauris. In aliquet pretium nisl, vel convallis neque cursus vitae. Curabitur id mauris in urna gravida ornare. + +[caption id="attachment_719" align="alignleft" width="150" caption="Ibanez AGB140 Bass"]<img class="size-thumbnail wp-image-719" title="Ibanez AGB140 Bass" src="http://www.mrclay.org/wp-content/uploads/2010/04/agb140-e1271773766573-150x150.jpg" alt="Ibanez AGB140 Bass" width="150" height="150" />[/caption] + +Aenean <a href="http://google.com/">aliquet cursus purus sed gravida. Cras auctor euismod justo, ac dictum purus facilisis dignissim.</a> Quisque facilisis porta sem, ac suscipit quam molestie nec. Pellentesque quis hendrerit enim. Vivamus tempor erat diam. Sed eu felis nunc. Cras posuere lorem commodo turpis mollis sagittis. Mauris lobortis nunc felis. + +Maecenas elit lorem, varius sed condimentum ac, cursus et magna. Nam ut massa id augue consectetur porttitor eleifend in nunc. Curabitur cursus varius dictum. Vestibulum vel justo et neque tempus placerat a vel sapien.</blockquote> +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus enim ante, mattis eget imperdiet nec, <a href="http://google.com/">pharetra </a>vel velit. Sed at euismod nibh. Praesent lacus tellus, posuere et convallis a, mollis et tellus. + +<pre><code><?php +class DataTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provider + */ + public function testAdd($a, $b, $c) + { + $this->assertEquals($c, $a + $b); + } + + public function provider() + { + return array( + array(0, 0, 0), + array(0, 1, 1), + array(1, 0, 1), + array(1, 1, 3) + ); + } +}</code></pre> +<ul> + <li>Suspendisse potenti. Phasellus tincidunt dignissim est eget mattis.</li> + <li>Vestibulum lacinia condimentum tellus, non vestibulum erat dapibus quis.</li> + <li>Aliquam arcu nibh, <a href="http://google.com/">viverra</a> adipiscing eleifend quis, pretium vitae ipsum.</li> + <li>Curabitur turpis ante, congue ac <a href="http://google.com/">dapibus quis</a>, vehicula ac orci.</li> +</ul> +Nunc luctus neque non massa porta sed pharetra ante accumsan. Nam suscipit risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor. Donec at massa ante, sagittis fermentum urna. + +<object width="480" height="390"><param name="movie" value="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&hl=en_US" /><param name="allowFullScreen" value="true" /><param name="allowscriptaccess" value="always" /><embed type="application/x-shockwave-flash" width="480" height="390" src="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&hl=en_US" allowfullscreen="true" allowscriptaccess="always"></embed></object> + +<h2>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h2> +<img class="alignright size-thumbnail wp-image-905" title="Surest Things mixing session in Adobe Audition" src="http://www.mrclay.org/wp-content/uploads/2010/09/surestThings_audition-150x150.png" alt="screenshot of Audition mixing session" width="150" height="150" />Vivamus enim ante, <em>mattis eget imperdiet nec, pharetra vel velit.</em> Sed at euismod nibh. Praesent lacus tellus, <a href="http://google.com/">posuere et convallis</a> a, <strong>mollis et tellus. Suspendisse potenti</strong>. Phasellus tincidunt dignissim est eget mattis. Vestibulum lacinia <del>condimentum tellus, non vestibulum erat dapibus</del> quis. Aliquam arcu nibh, viverra adipiscing eleifend quis, pretium vitae ipsum. + +Curabitur turpis ante, <span style="color: #993300;">congue ac dapibus quis, vehicula ac orci.</span> Nunc luctus neque non massa porta sed pharetra ante accumsan. <a href="http://google.com/">Nam suscipit</a> risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor. +<h3>Donec at massa ante, sagittis fermentum urna.</h3> +<blockquote>Mauris volutpat est id massa volutpat lacinia. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In in nisl mauris. In aliquet pretium nisl, vel convallis neque cursus vitae. Curabitur id mauris in urna gravida ornare. + +[caption id="attachment_719" align="alignleft" width="150" caption="Ibanez AGB140 Bass"]<img class="size-thumbnail wp-image-719" title="Ibanez AGB140 Bass" src="http://www.mrclay.org/wp-content/uploads/2010/04/agb140-e1271773766573-150x150.jpg" alt="Ibanez AGB140 Bass" width="150" height="150" />[/caption] + +Aenean <a href="http://google.com/">aliquet cursus purus sed gravida. Cras auctor euismod justo, ac dictum purus facilisis dignissim.</a> Quisque facilisis porta sem, ac suscipit quam molestie nec. Pellentesque quis hendrerit enim. Vivamus tempor erat diam. Sed eu felis nunc. Cras posuere lorem commodo turpis mollis sagittis. Mauris lobortis nunc felis. + +Maecenas elit lorem, varius sed condimentum ac, cursus et magna. Nam ut massa id augue consectetur porttitor eleifend in nunc. Curabitur cursus varius dictum. Vestibulum vel justo et neque tempus placerat a vel sapien.</blockquote> +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus enim ante, mattis eget imperdiet nec, <a href="http://google.com/">pharetra </a>vel velit. Sed at euismod nibh. Praesent lacus tellus, posuere et convallis a, mollis et tellus. + +<pre><code><?php +class DataTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provider + */ + public function testAdd($a, $b, $c) + { + $this->assertEquals($c, $a + $b); + } + + public function provider() + { + return array( + array(0, 0, 0), + array(0, 1, 1), + array(1, 0, 1), + array(1, 1, 3) + ); + } +}</code></pre> +<ul> + <li>Suspendisse potenti. Phasellus tincidunt dignissim est eget mattis.</li> + <li>Vestibulum lacinia condimentum tellus, non vestibulum erat dapibus quis.</li> + <li>Aliquam arcu nibh, <a href="http://google.com/">viverra</a> adipiscing eleifend quis, pretium vitae ipsum.</li> + <li>Curabitur turpis ante, congue ac <a href="http://google.com/">dapibus quis</a>, vehicula ac orci.</li> +</ul> +Nunc luctus neque non massa porta sed pharetra ante accumsan. Nam suscipit risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor. Donec at massa ante, sagittis fermentum urna. + +<object width="480" height="390"><param name="movie" value="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&hl=en_US" /><param name="allowFullScreen" value="true" /><param name="allowscriptaccess" value="always" /><embed type="application/x-shockwave-flash" width="480" height="390" src="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&hl=en_US" allowfullscreen="true" allowscriptaccess="always"></embed></object>
\ No newline at end of file diff --git a/engine/tests/test_files/output/autop/wp-welcome.exp.html b/engine/tests/test_files/output/autop/wp-welcome.exp.html new file mode 100644 index 000000000..2f612e3dd --- /dev/null +++ b/engine/tests/test_files/output/autop/wp-welcome.exp.html @@ -0,0 +1,22 @@ + +<p>Welcome to WordPress! This post contains important information. After you read it, you can make it private to hide it from visitors but still have the information handy for future reference.</p> + +<p>First things first:</p> +<ul><li><a href="%1%24s" title="Subscribe to the WordPress mailing list for Release Notifications">Subscribe to the WordPress mailing list for release notifications</a></li> +</ul> +<p>As a subscriber, you will receive an email every time an update is available (and only then). This will make it easier to keep your site up to date, and secure from evildoers.<br />When a new version is released, <a href="%2%24s" title="If you are already logged in, this will take you directly to the Dashboard">log in to the Dashboard</a> and follow the instructions.<br />Upgrading is a couple of clicks!</p> + +<p>Then you can start enjoying the WordPress experience:</p> +<ul><li>Edit your personal information at <a href="%3%24s" title="Edit settings like your password, your display name and your contact information">Users › Your Profile</a></li> + <li>Start publishing at <a href="%4%24s" title="Create a new post">Posts › Add New</a> and at <a href="%5%24s" title="Create a new page">Pages › Add New</a></li> + <li>Browse and install plugins at <a href="%6%24s" title="Browse and install plugins at the official WordPress repository directly from your Dashboard">Plugins › Add New</a></li> + <li>Browse and install themes at <a href="%7%24s" title="Browse and install themes at the official WordPress repository directly from your Dashboard">Appearance › Add New Themes</a></li> + <li>Modify and prettify your website’s links at <a href="%8%24s" title="For example, select a link structure like: http://example.com/1999/12/post-name">Settings › Permalinks</a></li> + <li>Import content from another system or WordPress site at <a href="%9%24s" title="WordPress comes with importers for the most common publishing systems">Tools › Import</a></li> + <li>Find answers to your questions at the <a href="%10%24s" title="The official WordPress documentation, maintained by the WordPress community">WordPress Codex</a></li> +</ul> +<p>To keep this post for reference, <a href="%11%24s" title="Click to edit the content and settings of this post">click to edit it</a>, go to the Publish box and change its Visibility from Public to Private.</p> + +<p>Thank you for selecting WordPress. We wish you happy publishing!</p> + +<p>PS. Not yet subscribed for update notifications? <a href="%1%24s" title="Subscribe to the WordPress mailing list for Release Notifications">Do it now!</a></p> diff --git a/engine/tests/test_files/output/autop/wp-welcome.in.html b/engine/tests/test_files/output/autop/wp-welcome.in.html new file mode 100644 index 000000000..338ede73f --- /dev/null +++ b/engine/tests/test_files/output/autop/wp-welcome.in.html @@ -0,0 +1,25 @@ +Welcome to WordPress! This post contains important information. After you read it, you can make it private to hide it from visitors but still have the information handy for future reference. + +First things first: +<ul> + <li><a href="%1$s" title="Subscribe to the WordPress mailing list for Release Notifications">Subscribe to the WordPress mailing list for release notifications</a></li> +</ul> +As a subscriber, you will receive an email every time an update is available (and only then). This will make it easier to keep your site up to date, and secure from evildoers. +When a new version is released, <a href="%2$s" title="If you are already logged in, this will take you directly to the Dashboard">log in to the Dashboard</a> and follow the instructions. +Upgrading is a couple of clicks! + +Then you can start enjoying the WordPress experience: +<ul> + <li>Edit your personal information at <a href="%3$s" title="Edit settings like your password, your display name and your contact information">Users › Your Profile</a></li> + <li>Start publishing at <a href="%4$s" title="Create a new post">Posts › Add New</a> and at <a href="%5$s" title="Create a new page">Pages › Add New</a></li> + <li>Browse and install plugins at <a href="%6$s" title="Browse and install plugins at the official WordPress repository directly from your Dashboard">Plugins › Add New</a></li> + <li>Browse and install themes at <a href="%7$s" title="Browse and install themes at the official WordPress repository directly from your Dashboard">Appearance › Add New Themes</a></li> + <li>Modify and prettify your website’s links at <a href="%8$s" title="For example, select a link structure like: http://example.com/1999/12/post-name">Settings › Permalinks</a></li> + <li>Import content from another system or WordPress site at <a href="%9$s" title="WordPress comes with importers for the most common publishing systems">Tools › Import</a></li> + <li>Find answers to your questions at the <a href="%10$s" title="The official WordPress documentation, maintained by the WordPress community">WordPress Codex</a></li> +</ul> +To keep this post for reference, <a href="%11$s" title="Click to edit the content and settings of this post">click to edit it</a>, go to the Publish box and change its Visibility from Public to Private. + +Thank you for selecting WordPress. We wish you happy publishing! + +PS. Not yet subscribed for update notifications? <a href="%1$s" title="Subscribe to the WordPress mailing list for Release Notifications">Do it now!</a> diff --git a/engine/tests/test_files/output/autop/wpautop-fails.exp.html b/engine/tests/test_files/output/autop/wpautop-fails.exp.html new file mode 100644 index 000000000..d018db4ff --- /dev/null +++ b/engine/tests/test_files/output/autop/wpautop-fails.exp.html @@ -0,0 +1,31 @@ + +<p>paragraph</p> + +<p>paragraph</p> +<div class="whatever"><blockquote> +<p>paragraph</p> +</blockquote> +<p>line</p> +</div> +<p>paragraph</p> +<ul><li>line</li> +<li>paragraph + +paragraph</li> +</ul> +<p>paragraph<br />line<br />line</p> +<pre>Honor +this whitespace +</pre> +<p>paragraph</p> +<style><!-- +Do not alter! +--></style> +<p>paragraph <!-- do not alter --></p> +<dl><dt>term</dt> <dd>paragraph + +<a href="xx"> <img src="yy"></a> + +paragraph</dd> </dl><div><a href="xx"> <img src="yy"></a></div> +<p>Hello <a href="link"><br /><br />World</a></p> +<p id="abc">Paragraph</p><div>Line</div>
\ No newline at end of file diff --git a/engine/tests/test_files/output/autop/wpautop-fails.in.html b/engine/tests/test_files/output/autop/wpautop-fails.in.html new file mode 100644 index 000000000..9aa24be59 --- /dev/null +++ b/engine/tests/test_files/output/autop/wpautop-fails.in.html @@ -0,0 +1,41 @@ + +paragraph + +paragraph <div class="whatever"><blockquote> + paragraph + </blockquote> + line +</div> + +paragraph +<ul> +<li>line</li> +<li>paragraph + +paragraph</li> +</ul> +paragraph +line<br> + line +<pre>Honor +this whitespace +</pre> +paragraph +<style><!-- +Do not alter! +--></style> +paragraph <!-- do not alter --> +<dl> <dt>term</dt> <dd>paragraph + +<a href="xx"> <img src="yy" /> </a> + +paragraph</dd> </dl> +<div><a href="xx"> <img src="yy" /> </a></div> + +Hello <a href="link"> + +World</a> + +<p id="abc">Paragraph</p> + +<div>Line</div>
\ No newline at end of file diff --git a/engine/tests/test_files/output/autop/wysiwyg-test.exp.html b/engine/tests/test_files/output/autop/wysiwyg-test.exp.html new file mode 100644 index 000000000..1f23d6154 --- /dev/null +++ b/engine/tests/test_files/output/autop/wysiwyg-test.exp.html @@ -0,0 +1,51 @@ + +<p>&nbps;<br />≴</p> +<h1>h1</h1> +<p>Paragraph <a href="http://google.com/">link</a> <strong>Bold</strong> <em>italic</em> <em><strong>bolditalic</strong></em> <span style="background-color: #ffff00; "></span></p> +<h2>h2</h2> +<p>Paragraph <span style="font-size: xx-small;">size1</span> <span style="font-size: x-small;">size2</span> <span style="font-size: medium;">size4</span></p> +<h3>h3</h3> +<p>Paragraph <span style="text-decoration: underline;">underline</span> <span style="text-decoration: line-through;">strikethrough</span> <span style="color: #ff0000;">color</span> <span style="background-color: #ffff00; ">background</span></p> +<blockquote> +<p>Blockquoted paragraph</p> +</blockquote> +<p>Paragraph following blockquote</p> +<ul><li>Unordered</li> + <li>List</li> +</ul> +<p>Paragraph between lists</p> +<ol><li>Ordered</li> + <li>List</li> +</ol> +<p>Paragraph between lists</p> +<ul><li>OL list</li> + <li>nested + <ol><li>inside a</li> + <li>UL list</li> + </ol></li> +</ul> +<p>Paragraph between lists</p> +<table border="0"><tbody><tr></tr><tr><td>Table with</td> + <td></td> + </tr><tr><td></td> + <td>border=0</td> + </tr></tbody></table> +<p>Paragraph</p> +<ol><li>UL list</li> + <li>nested + <ul><li>inside a</li> + <li>OL list</li> + </ul></li> +</ol> +<p>Paragraph between tables</p> +<table border="1" cellpadding="5"><tbody><tr><td>Table with border=1</td> + <td></td> + </tr><tr><td></td> + <td>cellpadding = 5</td> + </tr></tbody></table> +<p>Paragraph between tables</p> +<table border="2"><tbody><tr><td>Table with</td> + <td></td> + </tr><tr><td></td> + <td>border=2</td> + </tr></tbody></table>
\ No newline at end of file diff --git a/engine/tests/test_files/output/autop/wysiwyg-test.in.html b/engine/tests/test_files/output/autop/wysiwyg-test.in.html new file mode 100644 index 000000000..733b0e2ec --- /dev/null +++ b/engine/tests/test_files/output/autop/wysiwyg-test.in.html @@ -0,0 +1,79 @@ +&nbps; +≴ +<h1>h1</h1> +Paragraph <a href="http://google.com/">link</a> <strong>Bold</strong> <em>italic</em> <em><strong>bolditalic</strong></em> <span style="background-color: #ffff00; "></span> +<h2>h2</h2> +Paragraph <span style="font-size: xx-small;">size1</span> <span style="font-size: x-small;">size2</span> <span style="font-size: medium;">size4</span> +<h3>h3</h3> +Paragraph <span style="text-decoration: underline;">underline</span> <span style="text-decoration: line-through;">strikethrough</span> <span style="color: #ff0000;">color</span> <span style="background-color: #ffff00; ">background</span> +<blockquote>Blockquoted paragraph</blockquote> +Paragraph following blockquote +<ul> + <li>Unordered</li> + <li>List</li> +</ul> +Paragraph between lists +<ol> + <li>Ordered</li> + <li>List</li> +</ol> +Paragraph between lists +<ul> + <li>OL list</li> + <li>nested + <ol> + <li>inside a</li> + <li>UL list</li> + </ol></li> +</ul> +Paragraph between lists +<table border="0"> + <tbody> + <tr> + </tr> + <tr> + <td>Table with</td> + <td></td> + </tr> + <tr> + <td></td> + <td>border=0</td> + </tr> + </tbody> +</table> +Paragraph +<ol> + <li>UL list</li> + <li>nested + <ul> + <li>inside a</li> + <li>OL list</li> + </ul> + </li> +</ol> +Paragraph between tables +<table border="1" cellpadding="5"> + <tbody> + <tr> + <td>Table with border=1</td> + <td></td> + </tr> + <tr> + <td></td> + <td>cellpadding = 5</td> + </tr> + </tbody> +</table> +Paragraph between tables +<table border="2"> + <tbody> + <tr> + <td>Table with</td> + <td></td> + </tr> + <tr> + <td></td> + <td>border=2</td> + </tr> + </tbody> +</table>
\ No newline at end of file diff --git a/engine/tests/test_files/plugin_17/manifest.xml b/engine/tests/test_files/plugin_17/manifest.xml new file mode 100644 index 000000000..706734265 --- /dev/null +++ b/engine/tests/test_files/plugin_17/manifest.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<plugin_manifest> + <field key="author" value="Anyone" /> + <field key="version" value="1.0" /> + <field key="description" value="A 1.7-style manifest." /> + <field key="website" value="http://www.elgg.org/" /> + <field key="copyright" value="(C) Elgg Foundation 2011" /> + <field key="license" value="GNU General Public License version 2" /> + <field key="elgg_version" value="2009030702" /> +</plugin_manifest>
\ No newline at end of file diff --git a/engine/tests/test_files/plugin_17/start.php b/engine/tests/test_files/plugin_17/start.php new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/engine/tests/test_files/plugin_17/start.php diff --git a/engine/tests/test_files/plugin_18/manifest.xml b/engine/tests/test_files/plugin_18/manifest.xml new file mode 100644 index 000000000..c8b407511 --- /dev/null +++ b/engine/tests/test_files/plugin_18/manifest.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8"?> +<plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8"> + <name>Test Manifest</name> + <author>Anyone</author> + <version>1.0</version> + <blurb>A concise description.</blurb> + <description>A longer, more interesting description.</description> + <website>http://www.elgg.org/</website> + <repository>https://github.com/Elgg/Elgg</repository> + <bugtracker>https://github.com/elgg/elgg/issues</bugtracker> + <donations>http://elgg.org/supporter.php</donations> + <copyright>(C) Elgg Foundation 2011</copyright> + <license>GNU General Public License version 2</license> + + <requires> + <type>elgg_version</type> + <version>3009030802</version> + <comparison>lt</comparison> + </requires> + + <requires> + <type>elgg_release</type> + <version>1.8-svn</version> + </requires> + + <screenshot> + <description>Fun things to do 1</description> + <path>graphics/plugin_ss1.png</path> + </screenshot> + + <screenshot> + <description>Fun things to do 2</description> + <path>graphics/plugin_ss2.png</path> + </screenshot> + + <category>Admin</category> + + <category>ServiceAPI</category> + + <requires> + <type>php_extension</type> + <name>gd</name> + </requires> + + <requires> + <type>php_ini</type> + <name>short_open_tag</name> + <value>off</value> + </requires> + + <requires> + <type>php_extension</type> + <name>made_up</name> + <version>1.0</version> + </requires> + + <requires> + <type>plugin</type> + <name>fake_plugin</name> + <version>1.0</version> + </requires> + + <requires> + <type>plugin</type> + <name>profile</name> + <version>1.0</version> + </requires> + + <requires> + <type>plugin</type> + <name>profile_api</name> + <version>1.3</version> + <comparison>lt</comparison> + </requires> + + <requires> + <type>priority</type> + <priority>after</priority> + <plugin>profile</plugin> + </requires> + + <conflicts> + <type>plugin</type> + <name>profile_api</name> + <version>1.0</version> + </conflicts> + + <provides> + <type>plugin</type> + <name>profile_api</name> + <version>1.3</version> + </provides> + + <provides> + <type>php_extension</type> + <name>big_math</name> + <version>1.0</version> + </provides> + + <suggests> + <type>plugin</type> + <name>facebook_connect</name> + <version>1.0</version> + </suggests> + + <activate_on_install>true</activate_on_install> + +</plugin_manifest> diff --git a/engine/tests/test_files/plugin_18/start.php b/engine/tests/test_files/plugin_18/start.php new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/engine/tests/test_files/plugin_18/start.php diff --git a/engine/tests/test_files/xxe/external_entity.txt b/engine/tests/test_files/xxe/external_entity.txt new file mode 100644 index 000000000..536aca34d --- /dev/null +++ b/engine/tests/test_files/xxe/external_entity.txt @@ -0,0 +1 @@ +secret
\ No newline at end of file diff --git a/engine/tests/test_files/xxe/request.xml b/engine/tests/test_files/xxe/request.xml new file mode 100644 index 000000000..4390f9db2 --- /dev/null +++ b/engine/tests/test_files/xxe/request.xml @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<!DOCTYPE foo [ +<!ELEMENT methodName ANY > +<!ENTITY xxe SYSTEM "%s" > +]> +<methodCall> + <methodName>test&xxe;test</methodName> +</methodCall> diff --git a/engine/tests/test_skeleton.php b/engine/tests/test_skeleton.php index f26e0f6f9..5a5de89bb 100644 --- a/engine/tests/test_skeleton.php +++ b/engine/tests/test_skeleton.php @@ -5,7 +5,7 @@ * Plugin authors: copy this file to your plugin's test directory. Register an Elgg * plugin hook and function similar to: * - * register_plugin_hook('unit_test', 'system', 'my_new_unit_test'); + * elgg_register_plugin_hook_handler('unit_test', 'system', 'my_new_unit_test'); * * function my_new_unit_test($hook, $type, $value, $params) { * $value[] = "path/to/my/unit_test.php"; @@ -49,9 +49,6 @@ class ElggCoreSkeletonTest extends ElggCoreUnitTest { parent::__destruct(); } - /** - * A basic test that will be called and fail. - */ public function testFailure() { $this->assertTrue(FALSE); } diff --git a/engine/tests/ui/submenu.php b/engine/tests/ui/submenu.php deleted file mode 100644 index 3069e7405..000000000 --- a/engine/tests/ui/submenu.php +++ /dev/null @@ -1,97 +0,0 @@ -<?php -/** - * 1.8 submenu test. - * - * Submenu needs to be able to support being added out of order. - * Children can be added before parents. - * Children of parents never defined are never shown. - * - * Test against: - * different contexts - * different groups - * old add_submenu_item() wrapper. - * - */ - -require_once('../../start.php'); - -$url = "engine/tests/ui/submenu.php"; - -$items = array( - array( - 'text' => 'Upper level 1', - 'href' => "$url?upper_level_1", - 'id' => 'ul1' - ), - array( - 'text' => 'CD (No link)', - 'parent_id' => 'cup', - 'id' => 'cd', - ), - array( - 'text' => 'Sub CD', - 'href' => "$url?sub_cd", - 'parent_id' => 'cd' - ), - array( - 'text' => 'Cup', - 'href' => "$url?cup", - 'id' => 'cup' - ), - array( - 'text' => 'Phone', - 'href' => "$url?phone", - 'id' => 'phone', - 'parent_id' => 'cup' - ), - array( - 'text' => 'Wallet', - 'href' => "$url?wallet", - 'id' => 'wallet', - 'parent_id' => 'phone' - ), - array( - 'text' => 'Upper level', - 'href' => "$url?upper_level", - 'id' => 'ul' - ), - array( - 'text' => 'Sub Upper level', - 'href' => "$url?sub_upper_level", - 'parent_id' => 'ul' - ), - array( - 'text' => 'Root', - 'href' => $url, - ), - - array( - 'text' => 'I am an orphan', - 'href' => 'http://google.com', - 'parent_id' => 'missing_parent' - ), - - array( - 'text' => 'JS Test', - 'href' => 'http://elgg.org', - 'vars' => array('js' => 'onclick="alert(\'Link to \' + $(this).attr(\'href\') + \'!\'); return false;"') - ) -); - -foreach ($items as $item) { - elgg_add_submenu_item($item, 'main'); -} - -add_submenu_item('Old Onclick Test', 'http://elgg.com', NULL, TRUE); -add_submenu_item('Old Selected Test', 'http://elgg.com', NULL, '', TRUE); - - -elgg_add_submenu_item(array('text' => 'Not Main Test', 'href' => "$url?not_main_test"), 'not_main', 'new_menu'); -elgg_add_submenu_item(array('text' => 'Not Main C Test', 'href' => "$url?not_main_c_test"), 'not_main', 'new_menu'); - -elgg_add_submenu_item(array('text' => 'All test', 'href' => "$url?all"), 'all'); - -//elgg_set_context('not_main'); - -$body = elgg_view_layout('one_column_with_sidebar', 'Look right.'); -echo elgg_view_page('Submenu Test', $body); |
