diff options
Diffstat (limited to 'engine/classes')
91 files changed, 14283 insertions, 5371 deletions
diff --git a/engine/classes/APIException.php b/engine/classes/APIException.php new file mode 100644 index 000000000..b6e1c347b --- /dev/null +++ b/engine/classes/APIException.php @@ -0,0 +1,11 @@ +<?php + +/** + * API Exception Stub + * + * Generic parent class for API exceptions. + * + * @package Elgg.Core + * @subpackage Exceptions.Stub + */ +class APIException extends Exception {} diff --git a/engine/classes/CallException.php b/engine/classes/CallException.php new file mode 100644 index 000000000..22b8f14f5 --- /dev/null +++ b/engine/classes/CallException.php @@ -0,0 +1,10 @@ +<?php +/** + * Call Exception Stub + * + * Generic parent class for Call exceptions + * + * @package Elgg.Core + * @subpackage Exceptions.Stub + */ +class CallException extends Exception {} diff --git a/engine/classes/ClassException.php b/engine/classes/ClassException.php new file mode 100644 index 000000000..7544f0ec9 --- /dev/null +++ b/engine/classes/ClassException.php @@ -0,0 +1,10 @@ +<?php +/** + * Class Exception + * + * A generic parent class for Class exceptions + * + * @package Elgg.Core + * @subpackage Exceptions.Stub + */ +class ClassException extends Exception {} diff --git a/engine/classes/ClassNotFoundException.php b/engine/classes/ClassNotFoundException.php new file mode 100644 index 000000000..6a9bcd327 --- /dev/null +++ b/engine/classes/ClassNotFoundException.php @@ -0,0 +1,10 @@ +<?php +/** + * Class not found + * + * Thrown when trying to load a class that doesn't exist. + * + * @package Elgg.Core + * @subpackage Exceptions + */ +class ClassNotFoundException extends ClassException {} diff --git a/engine/classes/ConfigurationException.php b/engine/classes/ConfigurationException.php new file mode 100644 index 000000000..3ace5dd4b --- /dev/null +++ b/engine/classes/ConfigurationException.php @@ -0,0 +1,10 @@ +<?php +/** + * Configuration exception + * + * A generic parent class for Configuration exceptions + * + * @package Elgg + * @subpackage Exceptions.Stub + */ +class ConfigurationException extends Exception {} diff --git a/engine/classes/CronException.php b/engine/classes/CronException.php index 3720c2c59..86370ef31 100644 --- a/engine/classes/CronException.php +++ b/engine/classes/CronException.php @@ -1,3 +1,10 @@ -<?php
-/** The cron exception. */
-class CronException extends Exception {}
\ No newline at end of file +<?php +/** + * Cron exception + * + * A generic parent class for cron exceptions + * + * @package Elgg + * @subpackage Exceptions.Stub + */ +class CronException extends Exception {} diff --git a/engine/classes/DataFormatException.php b/engine/classes/DataFormatException.php new file mode 100644 index 000000000..0f28a0902 --- /dev/null +++ b/engine/classes/DataFormatException.php @@ -0,0 +1,9 @@ +<?php +/** + * Data format exception + * An exception thrown when there is a problem in the format of some data. + * + * @package Elgg.Core + * @subpackage Exceptions.Stub + */ +class DataFormatException extends Exception {} diff --git a/engine/classes/DatabaseException.php b/engine/classes/DatabaseException.php new file mode 100644 index 000000000..6c8f57d7d --- /dev/null +++ b/engine/classes/DatabaseException.php @@ -0,0 +1,10 @@ +<?php +/** + * Database Exception + * + * A generic parent class for database exceptions + * + * @package Elgg.Core + * @subpackage Exceptions.Stub + */ +class DatabaseException extends Exception {} diff --git a/engine/classes/ElggAccess.php b/engine/classes/ElggAccess.php index 57cceef03..0aed477fc 100644 --- a/engine/classes/ElggAccess.php +++ b/engine/classes/ElggAccess.php @@ -1,32 +1,70 @@ -<?php
-/**
- * Temporary class used to determing if access is being ignored
- */
-class ElggAccess {
- /**
- * Bypass Elgg's access control if true.
- * @var bool
- */
- private $ignore_access;
-
- /**
- * Get current ignore access setting.
- * @return bool
- */
- public function get_ignore_access() {
- return $this->ignore_access;
- }
-
- /**
- * Set ignore access.
- *
- * @param $ignore bool true || false to ignore
- * @return bool Previous setting
- */
- public function set_ignore_access($ignore = true) {
- $prev = $this->ignore_access;
- $this->ignore_access = $ignore;
-
- return $prev;
- }
-}
\ No newline at end of file +<?php +/** + * Class used to determine if access is being ignored. + * + * @package Elgg.Core + * @subpackage Access + * @access private + * @see elgg_get_ignore_access() + * + * @todo I don't remember why this was required beyond scope concerns. + */ +class ElggAccess { + /** + * Bypass Elgg's access control if true. + * @var bool + */ + private $ignore_access; + + // @codingStandardsIgnoreStart + /** + * Get current ignore access setting. + * + * @return bool + * @deprecated 1.8 Use ElggAccess::getIgnoreAccess() + */ + public function get_ignore_access() { + elgg_deprecated_notice('ElggAccess::get_ignore_access() is deprecated by ElggAccess::getIgnoreAccess()', 1.8); + return $this->getIgnoreAccess(); + } + // @codingStandardsIgnoreEnd + + /** + * Get current ignore access setting. + * + * @return bool + */ + public function getIgnoreAccess() { + return $this->ignore_access; + } + + // @codingStandardsIgnoreStart + /** + * Set ignore access. + * + * @param bool $ignore Ignore access + * + * @return bool Previous setting + * + * @deprecated 1.8 Use ElggAccess:setIgnoreAccess() + */ + public function set_ignore_access($ignore = true) { + elgg_deprecated_notice('ElggAccess::set_ignore_access() is deprecated by ElggAccess::setIgnoreAccess()', 1.8); + return $this->setIgnoreAccess($ignore); + } + // @codingStandardsIgnoreEnd + + /** + * Set ignore access. + * + * @param bool $ignore Ignore access + * + * @return bool Previous setting + */ + public function setIgnoreAccess($ignore = true) { + $prev = $this->ignore_access; + $this->ignore_access = $ignore; + + return $prev; + } +} diff --git a/engine/classes/ElggAnnotation.php b/engine/classes/ElggAnnotation.php index fe85ca082..175e7049d 100644 --- a/engine/classes/ElggAnnotation.php +++ b/engine/classes/ElggAnnotation.php @@ -1,107 +1,133 @@ -<?php
-/**
- * ElggAnnotation
- *
- * An annotation is similar to metadata.
- * Each entity can have more than one of each type of annotation.
- *
- * @package Elgg
- * @subpackage Core
- * @author Curverider Ltd <info@elgg.com>
- */
-class ElggAnnotation extends ElggExtender {
-
- /**
- * Construct a new annotation, optionally from a given id value or db object.
- *
- * @param mixed $id
- */
- function __construct($id = null) {
- $this->attributes = array();
-
- if (!empty($id)) {
- 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;
- }
-
- $this->attributes['type'] = "annotation";
- }
- }
- }
-
- /**
- * Class member get overloading
- *
- * @param string $name
- * @return mixed
- */
- function __get($name) {
- return $this->get($name);
- }
-
- /**
- * Class member set overloading
- *
- * @param string $name
- * @param mixed $value
- * @return void
- */
- function __set($name, $value) {
- return $this->set($name, $value);
- }
-
- /**
- * Save this instance
- *
- * @return int an object id
- */
- function save() {
- if ($this->id > 0) {
- return update_annotation($this->id, $this->name, $this->value, $this->value_type, $this->owner_guid, $this->access_id);
- } else {
- $this->id = create_annotation($this->entity_guid, $this->name, $this->value,
- $this->value_type, $this->owner_guid, $this->access_id);
-
- if (!$this->id) {
- throw new IOException(sprintf(elgg_echo('IOException:UnableToSaveNew'), get_class()));
- }
- return $this->id;
- }
- }
-
- /**
- * Delete the annotation.
- */
- function delete() {
- return delete_annotation($this->id);
- }
-
- /**
- * Get a url for this annotation.
- *
- * @return string
- */
- public function getURL() {
- return get_annotation_url($this->id);
- }
-
- // SYSTEM LOG INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * For a given ID, return the object associated with it.
- * This is used by the river functionality primarily.
- * This is useful for checking access permissions etc on objects.
- */
- public function getObjectFromID($id) {
- return get_annotation($id);
- }
-}
\ No newline at end of file +<?php +/** + * Elgg Annotations + * + * Annotations allow you to attach bits of information to entities. + * They are essentially the same as metadata, but with additional + * helper functions. + * + * @internal Annotations are stored in the annotations table. + * + * @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 object + * + * @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; + + $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; + } + } + } + + /** + * Save this instance + * + * @return int an object id + * + * @throws IOException + */ + function save() { + if ($this->id > 0) { + return update_annotation($this->id, $this->name, $this->value, $this->value_type, + $this->owner_guid, $this->access_id); + } else { + $this->id = create_annotation($this->entity_guid, $this->name, $this->value, + $this->value_type, $this->owner_guid, $this->access_id); + + if (!$this->id) { + throw new IOException(elgg_echo('IOException:UnableToSaveNew', array(get_class()))); + } + return $this->id; + } + } + + /** + * Delete the annotation. + * + * @return bool + */ + function delete() { + 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'); + } + + /** + * Get a url for this annotation. + * + * @return string + */ + public function getURL() { + return get_annotation_url($this->id); + } + + // SYSTEM LOG INTERFACE + + /** + * For a given ID, return the object associated with it. + * This is used by the river functionality primarily. + * This is useful for checking access permissions etc on objects. + * + * @param int $id An annotation ID. + * + * @return ElggAnnotation + */ + public function getObjectFromID($id) { + return elgg_get_annotation_from_id($id); + } +} 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 c59285467..909eab39b 100644 --- a/engine/classes/ElggCache.php +++ b/engine/classes/ElggCache.php @@ -1,164 +1,247 @@ -<?php
-
-/**
- * ElggCache The elgg cache superclass.
- * This defines the interface for a cache (wherever that cache is stored).
- *
- * @author Curverider Ltd <info@elgg.com>
- * @package Elgg
- * @subpackage API
- */
-abstract class ElggCache implements
- // Override for array access
- ArrayAccess {
- /**
- * Variables for the cache object.
- *
- * @var array
- */
- private $variables;
-
- /**
- * Set the constructor.
- */
- function __construct() {
- $this->variables = array();
- }
-
- /**
- * Set a cache variable.
- *
- * @param string $variable
- * @param string $value
- */
- public function set_variable($variable, $value) {
- if (!is_array($this->variables)) {
- $this->variables = array();
- }
-
- $this->variables[$variable] = $value;
- }
-
- /**
- * Get variables for this cache.
- *
- * @param string $variable
- * @return mixed The variable or null;
- */
- public function get_variable($variable) {
- if (isset($this->variables[$variable])) {
- return $this->variables[$variable];
- }
-
- return null;
- }
-
- /**
- * Class member get overloading, returning key using $this->load defaults.
- *
- * @param string $key
- * @return mixed
- */
- function __get($key) {
- return $this->load($key);
- }
-
- /**
- * Class member set overloading, setting a key using $this->save defaults.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- function __set($key, $value) {
- return $this->save($key, $value);
- }
-
- /**
- * Supporting isset, using $this->load() with default values.
- *
- * @param string $key The name of the attribute or metadata.
- * @return bool
- */
- function __isset($key) {
- return (bool)$this->load($key);
- }
-
- /**
- * Supporting unsetting of magic attributes.
- *
- * @param string $key The name of the attribute or metadata.
- */
- function __unset($key) {
- return $this->delete($key);
- }
-
- /**
- * Save data in a cache.
- *
- * @param string $key
- * @param string $data
- * @return bool
- */
- abstract public function save($key, $data);
-
- /**
- * Load data from the cache using a given key.
- *
- * @param string $key
- * @param int $offset
- * @param int $limit
- * @return mixed The stored data or false.
- */
- abstract public function load($key, $offset = 0, $limit = null);
-
- /**
- * Invalidate a key
- *
- * @param string $key
- * @return bool
- */
- abstract public function delete($key);
-
- /**
- * Clear out all the contents of the cache.
- *
- */
- abstract public function clear();
-
- /**
- * Add a key only if it doesn't already exist.
- * Implemented simply here, if you extend this class and your caching engine provides a better way then
- * override this accordingly.
- *
- * @param string $key
- * @param string $data
- * @return bool
- */
- public function add($key, $data) {
- if (!isset($this[$key])) {
- return $this->save($key, $data);
- }
-
- return false;
- }
-
- // ARRAY ACCESS INTERFACE //////////////////////////////////////////////////////////
- function offsetSet($key, $value) {
- $this->save($key, $value);
- }
-
- function offsetGet($key) {
- return $this->load($key);
- }
-
- function offsetUnset($key) {
- if ( isset($this->key) ) {
- unset($this->key);
- }
- }
-
- function offsetExists($offset) {
- return isset($this->$offset);
- }
-}
\ No newline at end of file +<?php +/** + * ElggCache The elgg cache superclass. + * This defines the interface for a cache (wherever that cache is stored). + * + * @package Elgg.Core + * @subpackage Cache + */ +abstract class ElggCache implements ArrayAccess { + /** + * Variables for the cache object. + * + * @var array + */ + private $variables; + + /** + * Set the constructor. + */ + function __construct() { + $this->variables = array(); + } + + // @codingStandardsIgnoreStart + /** + * Set a cache variable. + * + * @param string $variable Name + * @param string $value Value + * + * @return void + * + * @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. + * + * @param string $variable Name + * @param string $value Value + * + * @return void + */ + public function setVariable($variable, $value) { + if (!is_array($this->variables)) { + $this->variables = array(); + } + + $this->variables[$variable] = $value; + } + + // @codingStandardsIgnoreStart + /** + * Get variables for this cache. + * + * @param string $variable Name + * + * @return mixed The value or null; + * + * @deprecated 1.8 Use ElggCache::getVariable() + */ + public function get_variable($variable) { + elgg_deprecated_notice('ElggCache::get_variable() is deprecated by ElggCache::getVariable()', 1.8); + return $this->getVariable($variable); + } + // @codingStandardsIgnoreEnd + + /** + * Get variables for this cache. + * + * @param string $variable Name + * + * @return mixed The variable or null; + */ + public function getVariable($variable) { + if (isset($this->variables[$variable])) { + return $this->variables[$variable]; + } + + return null; + } + + /** + * Class member get overloading, returning key using $this->load defaults. + * + * @param string $key Name + * + * @return mixed + */ + function __get($key) { + return $this->load($key); + } + + /** + * Class member set overloading, setting a key using $this->save defaults. + * + * @param string $key Name + * @param mixed $value Value + * + * @return mixed + */ + function __set($key, $value) { + return $this->save($key, $value); + } + + /** + * Supporting isset, using $this->load() with default values. + * + * @param string $key The name of the attribute or metadata. + * + * @return bool + */ + function __isset($key) { + return (bool)$this->load($key); + } + + /** + * Supporting unsetting of magic attributes. + * + * @param string $key The name of the attribute or metadata. + * + * @return bool + */ + function __unset($key) { + return $this->delete($key); + } + + /** + * Save data in a cache. + * + * @param string $key Name + * @param string $data Value + * + * @return bool + */ + abstract public function save($key, $data); + + /** + * 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 + * + * @return mixed The stored data or false. + */ + abstract public function load($key, $offset = 0, $limit = null); + + /** + * Invalidate a key + * + * @param string $key Name + * + * @return bool + */ + abstract public function delete($key); + + /** + * Clear out all the contents of the cache. + * + * @return bool + */ + abstract public function clear(); + + /** + * Add a key only if it doesn't already exist. + * Implemented simply here, if you extend this class and your caching engine + * provides a better way then override this accordingly. + * + * @param string $key Name + * @param string $data Value + * + * @return bool + */ + public function add($key, $data) { + if (!isset($this[$key])) { + return $this->save($key, $data); + } + + return false; + } + + // ARRAY ACCESS INTERFACE ////////////////////////////////////////////////////////// + + /** + * Assigns a value for the specified key + * + * @see ArrayAccess::offsetSet() + * + * @param mixed $key The key (offset) to assign the value to. + * @param mixed $value The value to set. + * + * @return void + */ + function offsetSet($key, $value) { + $this->save($key, $value); + } + + /** + * Get the value for specified key + * + * @see ArrayAccess::offsetGet() + * + * @param mixed $key The key (offset) to retrieve. + * + * @return mixed + */ + function offsetGet($key) { + return $this->load($key); + } + + /** + * Unsets a key. + * + * @see ArrayAccess::offsetUnset() + * + * @param mixed $key The key (offset) to unset. + * + * @return void + */ + function offsetUnset($key) { + if (isset($this->$key)) { + unset($this->$key); + } + } + + /** + * Does key exist + * + * @see ArrayAccess::offsetExists() + * + * @param mixed $key A key (offset) to check for. + * + * @return bool + */ + function offsetExists($key) { + return isset($this->$key); + } +} 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 new file mode 100644 index 000000000..4f843cde4 --- /dev/null +++ b/engine/classes/ElggData.php @@ -0,0 +1,309 @@ +<?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 + * This contains the site's main properties (id, etc) + * Blank entries for all database fields should be created by the constructor. + * Subclasses should add to this in their constructors. + * 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. + * + * This is vital to distinguish between metadata and base parameters. + * + * @return void + */ + protected function initializeAttributes() { + // Create attributes array if not already created + if (!is_array($this->attributes)) { + $this->attributes = array(); + } + + $this->attributes['time_created'] = NULL; + } + + /** + * Return an attribute or a piece of metadata. + * + * @param string $name Name + * + * @return mixed + */ + public function __get($name) { + return $this->get($name); + } + + /** + * Set an attribute or a piece of metadata. + * + * @param string $name Name + * @param mixed $value Value + * + * @return mixed + */ + public function __set($name, $value) { + return $this->set($name, $value); + } + + /** + * Test if property is set either as an attribute or metadata. + * + * @tip Use isset($entity->property) + * + * @param string $name The name of the attribute or metadata. + * + * @return bool + */ + 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. + * + * @return bool + */ + abstract public function save(); + + /** + * Delete this data. + * + * @return bool + */ + abstract public function delete(); + + /** + * Returns the UNIX epoch time that this entity was created + * + * @return int UNIX epoch time + */ + public function getTimeCreated() { + return $this->time_created; + } + + /* + * SYSTEM LOG INTERFACE + */ + + /** + * Return the class name of the object. + * + * @return string + */ + public function getClassName() { + return get_class($this); + } + + /** + * Return the GUID of the owner of this object. + * + * @return int + * @deprecated 1.8 Use getOwnerGUID() instead + */ + public function getObjectOwnerGUID() { + elgg_deprecated_notice("getObjectOwnerGUID() was deprecated. Use getOwnerGUID().", 1.8); + return $this->owner_guid; + } + + /* + * ITERATOR INTERFACE + */ + + /* + * This lets an entity's attributes be displayed using foreach as a normal array. + * Example: http://www.sitepoint.com/print/php5-standard-library + */ + protected $valid = FALSE; + + /** + * Iterator interface + * + * @see Iterator::rewind() + * + * @return void + */ + public function rewind() { + $this->valid = (FALSE !== reset($this->attributes)); + } + + /** + * Iterator interface + * + * @see Iterator::current() + * + * @return mixed + */ + public function current() { + return current($this->attributes); + } + + /** + * Iterator interface + * + * @see Iterator::key() + * + * @return string + */ + public function key() { + return key($this->attributes); + } + + /** + * Iterator interface + * + * @see Iterator::next() + * + * @return void + */ + public function next() { + $this->valid = (FALSE !== next($this->attributes)); + } + + /** + * Iterator interface + * + * @see Iterator::valid() + * + * @return bool + */ + public function valid() { + return $this->valid; + } + + /* + * ARRAY ACCESS INTERFACE + */ + + /* + * This lets an entity's attributes be accessed like an associative array. + * Example: http://www.sitepoint.com/print/php5-standard-library + */ + + /** + * Array access interface + * + * @see ArrayAccess::offsetSet() + * + * @param mixed $key Name + * @param mixed $value Value + * + * @return void + */ + public function offsetSet($key, $value) { + if (array_key_exists($key, $this->attributes)) { + $this->attributes[$key] = $value; + } + } + + /** + * Array access interface + * + * @see ArrayAccess::offsetGet() + * + * @param mixed $key Name + * + * @return mixed + */ + public function offsetGet($key) { + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + return null; + } + + /** + * Array access interface + * + * @see ArrayAccess::offsetUnset() + * + * @param mixed $key Name + * + * @return void + */ + public function offsetUnset($key) { + if (array_key_exists($key, $this->attributes)) { + // Full unsetting is dangerous for our objects + $this->attributes[$key] = ""; + } + } + + /** + * Array access interface + * + * @see ArrayAccess::offsetExists() + * + * @param int $offset Offset + * + * @return int + */ + public function offsetExists($offset) { + return array_key_exists($offset, $this->attributes); + } +} diff --git a/engine/classes/ElggDiskFilestore.php b/engine/classes/ElggDiskFilestore.php index 6b0fa2554..6e2354012 100644 --- a/engine/classes/ElggDiskFilestore.php +++ b/engine/classes/ElggDiskFilestore.php @@ -1,8 +1,13 @@ <?php /** - * @class ElggDiskFilestore - * This class uses disk storage to save data. - * @author Curverider Ltd + * A filestore that uses disk as storage. + * + * @warning This should be used by a wrapper class + * like {@link ElggFile}. + * + * @package Elgg.Core + * @subpackage FileStore.Disk + * @link http://docs.elgg.org/DataModel/FileStore/Disk */ class ElggDiskFilestore extends ElggFilestore { /** @@ -30,26 +35,41 @@ class ElggDiskFilestore extends ElggFilestore { } } + /** + * Open a file for reading, writing, or both. + * + * @note All files are opened binary safe. + * @warning This will try to create the a directory if it doesn't exist, + * even in read-only mode. + * + * @param ElggFile $file The file to open + * @param string $mode read, write, or append. + * + * @throws InvalidParameterException + * @return resource File pointer resource + * @todo This really shouldn't try to create directories if not writing. + */ public function open(ElggFile $file, $mode) { $fullname = $this->getFilenameOnFilestore($file); // Split into path and name - $ls = strrpos($fullname,"/"); - if ($ls===false) { + $ls = strrpos($fullname, "/"); + if ($ls === false) { $ls = 0; } $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) { } - if (($mode!='write') && (!file_exists($fullname))) { + if (($mode != 'write') && (!file_exists($fullname))) { return false; } @@ -64,17 +84,35 @@ class ElggDiskFilestore extends ElggFilestore { $mode = "a+b"; break; default: - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:UnrecognisedFileMode'), $mode)); + $msg = elgg_echo('InvalidParameterException:UnrecognisedFileMode', array($mode)); + throw new InvalidParameterException($msg); } return fopen($fullname, $mode); } + /** + * Write data to a file. + * + * @param resource $f File pointer resource + * @param mixed $data The data to write. + * + * @return bool + */ public function write($f, $data) { return fwrite($f, $data); } + /** + * Read data from a file. + * + * @param resource $f File pointer resource + * @param int $length The number of bytes to read + * @param int $offset The number of bytes to start after + * + * @return mixed Contents of file or false on fail. + */ public function read($f, $length, $offset = 0) { if ($offset) { $this->seek($f, $offset); @@ -83,10 +121,24 @@ class ElggDiskFilestore extends ElggFilestore { return fread($f, $length); } + /** + * Close a file pointer + * + * @param resource $f A file pointer resource + * + * @return bool + */ public function close($f) { return fclose($f); } + /** + * Delete an ElggFile file. + * + * @param ElggFile $file File to delete + * + * @return bool + */ public function delete(ElggFile $file) { $filename = $this->getFilenameOnFilestore($file); if (file_exists($filename)) { @@ -96,77 +148,173 @@ class ElggDiskFilestore extends ElggFilestore { } } + /** + * Seek to the specified position. + * + * @param resource $f File resource + * @param int $position Position in bytes + * + * @return bool + */ public function seek($f, $position) { return fseek($f, $position); } + /** + * Return the current location of the internal pointer + * + * @param resource $f File pointer resource + * + * @return int|false + */ public function tell($f) { return ftell($f); } + /** + * Tests for end of file on a file pointer + * + * @param resource $f File pointer resource + * + * @return bool + */ public function eof($f) { return feof($f); } + /** + * Returns the file size of an ElggFile file. + * + * @param ElggFile $file File object + * + * @return int The file size + */ public function getFileSize(ElggFile $file) { return filesize($this->getFilenameOnFilestore($file)); } + /** + * 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_guid) { + $msg = elgg_echo('InvalidParameterException:MissingOwner', + array($file->getFilename(), $file->guid)); + throw new InvalidParameterException($msg); } - if ((!$owner) || (!$owner->username)) { - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:MissingOwner'), $file->getFilename(), $file->guid)); + $filename = $file->getFilename(); + if (!$filename) { + return ''; } - return $this->dir_root . $this->make_file_matrix($owner->guid) . $file->getFilename(); + return $this->dir_root . $this->makeFileMatrix($owner_guid) . $filename; } + /** + * Returns the contents of the ElggFile file. + * + * @param ElggFile $file File object + * + * @return string + */ public function grabFile(ElggFile $file) { return file_get_contents($file->getFilenameOnFilestore()); } + /** + * Tests if an ElggFile file exists. + * + * @param ElggFile $file File object + * + * @return bool + */ public function exists(ElggFile $file) { + if (!$file->getFilename()) { + return false; + } return file_exists($this->getFilenameOnFilestore($file)); } - public function getSize($prefix,$container_guid) { + /** + * Returns the size of all data stored under a directory in the disk store. + * + * @param string $prefix Optional/ The prefix to check under. + * @param string $container_guid The guid of the entity whose data you want to check. + * + * @return int|false + */ + 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 /** - * Make the directory root. + * Create a directory $dirroot * - * @param string $dirroot + * @param string $dirroot The full path of the directory to create + * + * @throws IOException + * @return true + * @deprecated 1.8 Use ElggDiskFilestore::makeDirectoryRoot() */ protected function make_directory_root($dirroot) { + elgg_deprecated_notice('ElggDiskFilestore::make_directory_root() is deprecated by ::makeDirectoryRoot()', 1.8); + + return $this->makeDirectoryRoot($dirroot); + } + // @codingStandardsIgnoreEnd + + /** + * Create a directory $dirroot + * + * @param string $dirroot The full path of the directory to create + * + * @throws IOException + * @return true + */ + 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. * - * Splits a string into an array. Will fail safely if mbstring is not installed (although this may still - * not handle . + * Splits a string into an array. Will fail safely if mbstring is + * not installed. * - * @param string $string String + * @param string $string String * @param string $charset The charset, defaults to UTF8 + * * @return array + * @deprecated 1.8 Files are stored by date and guid; no need for this. */ private function mb_str_split($string, $charset = 'UTF8') { + elgg_deprecated_notice('ElggDiskFilestore::mb_str_split() is deprecated.', 1.8); + if (is_callable('mb_substr')) { $length = mb_strlen($string); $array = array(); @@ -182,76 +330,82 @@ class ElggDiskFilestore extends ElggFilestore { } else { return str_split($string); } - - return false; } + // @codingStandardsIgnoreEnd + // @codingStandardsIgnoreStart /** - * Construct the filename matrix. + * Construct a file path matrix for an entity. + * + * @param int $identifier The guide of the entity to store the data under. * - * @param int | string $identifier - * @return str + * @return string The path where the entity's data will be stored. + * @deprecated 1.8 Use ElggDiskFilestore::makeFileMatrix() */ protected function make_file_matrix($identifier) { - if (is_numeric($identifier)) { - return $this->user_file_matrix($identifier); - } + elgg_deprecated_notice('ElggDiskFilestore::make_file_matrix() is deprecated by ::makeFileMatrix()', 1.8); - return $this->deprecated_file_matrix($identifier); + return $this->makeFileMatrix($identifier); } + // @codingStandardsIgnoreEnd /** - * Construct the filename matrix with user info + * Construct a file path matrix for an entity. * - * This method will generate a matrix using the entity's creation time and - * unique guid. This is intended only to determine a user's data directory. + * @param int $guid The guide of the entity to store the data under. * - * @param int $guid - * @return str + * @return string The path where the entity's data will be stored. */ - protected function user_file_matrix($guid) { - // lookup the entity - $user = get_entity($guid); - if ($user->type != 'user') - { - // only to be used for user directories - return FALSE; - } + protected function makeFileMatrix($guid) { + $entity = get_entity($guid); - if (!$user->time_created) { - // fall back to deprecated method - return $this->deprecated_file_matrix($user->username); + if (!($entity instanceof ElggEntity) || !$entity->time_created) { + return false; } - $time_created = date('Y/m/d', $user->time_created); - return "$time_created/$user->guid/"; + $time_created = date('Y/m/d', $entity->time_created); + + return "$time_created/$entity->guid/"; } + // @codingStandardsIgnoreStart /** - * Construct the filename matrix using a string + * Construct a filename matrix. + * + * Generates a matrix using the entity's creation time and + * unique guid. * - * Particularly, this is used with a username to generate the file storage - * location. + * File path matrixes are: + * YYYY/MM/DD/guid/ * - * @deprecated for user directories: use user_file_matrix() instead. + * @param int $guid The entity to contrust a matrix for * - * @param str $filename - * @return str + * @return string The */ - protected function deprecated_file_matrix($filename) { - // throw a warning for using deprecated method - $error = 'Deprecated use of ElggDiskFilestore::make_file_matrix. '; - $error .= 'Username passed instead of guid.'; - elgg_log($error, WARNING); - - $user = new ElggUser($filename); - return $this->user_file_matrix($user->guid); + 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 + * the ElggFile object using this file store. + * + * @return array + */ public function getParameters() { return array("dir_root" => $this->dir_root); } + /** + * Sets parameters that should be saved to database. + * + * @param array $parameters Set parameters to save to DB for this filestore. + * + * @return bool + */ public function setParameters(array $parameters) { if (isset($parameters['dir_root'])) { $this->dir_root = $parameters['dir_root']; diff --git a/engine/classes/ElggEntity.php b/engine/classes/ElggEntity.php index f0d58d1bd..a563f6fad 100644 --- a/engine/classes/ElggEntity.php +++ b/engine/classes/ElggEntity.php @@ -1,1210 +1,1770 @@ -<?php
-/**
- * ElggEntity The elgg entity superclass
- * This class holds methods for accessing the main entities table.
- *
- * @author Curverider Ltd <info@elgg.com>
- * @package Elgg
- * @subpackage Core
- */
-abstract class ElggEntity implements
- Notable, // Calendar interface
- Locatable, // Geocoding interface
- Exportable, // Allow export of data
- Importable, // Allow import of data
- Loggable, // Can events related to this object class be logged
- Iterator, // Override foreach behaviour
- ArrayAccess // Override for array access
-{
- /**
- * The main attributes of an entity.
- * Blank entries for all database fields should be created by the constructor.
- * Subclasses should add to this in their constructors.
- * Any field not appearing in this will be viewed as a
- */
- protected $attributes;
-
- /**
- * If set, overrides the value of getURL()
- */
- protected $url_override;
-
- /**
- * Icon override, overrides the value of getIcon().
- */
- protected $icon_override;
-
- /**
- * Temporary cache for metadata, permitting meta data access before a guid has obtained.
- */
- protected $temp_metadata;
-
- /**
- * Temporary cache for annotations, permitting meta data access before a guid has obtained.
- */
- protected $temp_annotations;
-
-
- /**
- * Volatile data structure for this object, allows for storage of data
- * in-memory that isn't sync'd back to the metadata table.
- */
- protected $volatile;
-
- /**
- * Initialise the attributes array.
- * This is vital to distinguish between metadata and base parameters.
- *
- * Place your base parameters here.
- *
- * @return void
- */
- protected function initialise_attributes() {
- initialise_entity_cache();
-
- // Create attributes array if not already created
- if (!is_array($this->attributes)) {
- $this->attributes = array();
- }
- if (!is_array($this->temp_metadata)) {
- $this->temp_metadata = array();
- }
- if (!is_array($this->temp_annotations)) {
- $this->temp_annotations = array();
- }
- if (!is_array($this->volatile)) {
- $this->volatile = array();
- }
-
- $this->attributes['guid'] = "";
- $this->attributes['type'] = "";
- $this->attributes['subtype'] = "";
-
- $this->attributes['owner_guid'] = get_loggedin_userid();
- $this->attributes['container_guid'] = get_loggedin_userid();
-
- $this->attributes['site_guid'] = 0;
- $this->attributes['access_id'] = ACCESS_PRIVATE;
- $this->attributes['time_created'] = "";
- $this->attributes['time_updated'] = "";
- $this->attributes['last_action'] = '';
- $this->attributes['enabled'] = "yes";
-
- // There now follows a bit of a hack
- /* Problem: To speed things up, some objects are split over several tables, this means that it requires
- * n number of database reads to fully populate an entity. This causes problems for caching and create events
- * since it is not possible to tell whether a subclassed entity is complete.
- * Solution: We have two counters, one 'tables_split' which tells whatever is interested how many tables
- * are going to need to be searched in order to fully populate this object, and 'tables_loaded' which is how
- * many have been loaded thus far.
- * If the two are the same then this object is complete.
- *
- * Use: isFullyLoaded() to check
- */
- $this->attributes['tables_split'] = 1;
- $this->attributes['tables_loaded'] = 0;
- }
-
- /**
- * Clone an entity
- *
- * Resets the guid so that the entity can be saved as a distinct entity from
- * the original. Creation time will be set when this new entity is saved.
- * The owner and container guids come from the original entity. The clone
- * method copies metadata but does not copy over annotations, or private settings.
- *
- * Note: metadata will have its owner and access id set when the entity is saved
- * and it will be the same as that of the entity.
- */
- public function __clone() {
-
- $orig_entity = get_entity($this->guid);
- if (!$orig_entity) {
- elgg_log("Failed to clone entity with GUID $this->guid", "ERROR");
- return;
- }
-
- $metadata_array = get_metadata_for_entity($this->guid);
-
- $this->attributes['guid'] = "";
-
- $this->attributes['subtype'] = $orig_entity->getSubtype();
-
- // copy metadata over to new entity - slightly convoluted due to
- // handling of metadata arrays
- if (is_array($metadata_array)) {
- // create list of metadata names
- $metadata_names = array();
- foreach ($metadata_array as $metadata) {
- $metadata_names[] = $metadata['name'];
- }
- // arrays are stored with multiple enties per name
- $metadata_names = array_unique($metadata_names);
-
- // move the metadata over
- foreach ($metadata_names as $name) {
- $this->set($name, $orig_entity->$name);
- }
- }
- }
-
- /**
- * Return the value of a given key.
- * If $name is a key field (as defined in $this->attributes) that value is returned, otherwise it will
- * then look to see if the value is in this object's metadata.
- *
- * Q: Why are we not using __get overload here?
- * A: Because overload operators cause problems during subclassing, so we put the code here and
- * create overloads in subclasses.
- *
- * subtype is returned as an id rather than the subtype string. Use getSubtype()
- * to get the subtype string.
- *
- * @param string $name
- * @return mixed Returns the value of a given value, or null.
- */
- public function get($name) {
- // See if its in our base attribute
- if (isset($this->attributes[$name])) {
- return $this->attributes[$name];
- }
-
- // No, so see if its in the meta data for this entity
- $meta = $this->getMetaData($name);
-
- // getMetaData returns NULL if $name is not found
- return $meta;
- }
-
- /**
- * Set the value of a given key, replacing it if necessary.
- * If $name is a base attribute (as defined in $this->attributes) that value is set, otherwise it will
- * set the appropriate item of metadata.
- *
- * Note: It is important that your class populates $this->attributes with keys for all base attributes, anything
- * not in their gets set as METADATA.
- *
- * Q: Why are we not using __set overload here?
- * A: Because overload operators cause problems during subclassing, so we put the code here and
- * create overloads in subclasses.
- *
- * @param string $name
- * @param mixed $value
- */
- public function set($name, $value) {
- if (array_key_exists($name, $this->attributes)) {
- // Certain properties should not be manually changed!
- switch ($name) {
- case 'guid':
- case 'time_created':
- case 'time_updated':
- case 'last_action':
- return FALSE;
- break;
- default:
- $this->attributes[$name] = $value;
- break;
- }
- } else {
- return $this->setMetaData($name, $value);
- }
-
- return TRUE;
- }
-
- /**
- * Get a given piece of metadata.
- *
- * @param string $name
- */
- public function getMetaData($name) {
- if ((int) ($this->guid) > 0) {
- $md = get_metadata_byname($this->getGUID(), $name);
- } else {
- if (isset($this->temp_metadata[$name])) {
- return $this->temp_metadata[$name];
- }
- }
-
- if ($md && !is_array($md)) {
- return $md->value;
- } else if ($md && is_array($md)) {
- return metadata_array_to_values($md);
- }
-
- return null;
- }
-
- /**
- * Class member get overloading
- *
- * @param string $name
- * @return mixed
- */
- function __get($name) {
- return $this->get($name);
- }
-
- /**
- * Class member set overloading
- *
- * @param string $name
- * @param mixed $value
- * @return mixed
- */
- function __set($name, $value) {
- return $this->set($name, $value);
- }
-
- /**
- * Supporting isset.
- *
- * @param string $name The name of the attribute or metadata.
- * @return bool
- */
- function __isset($name) {
- return $this->$name !== NULL;
- }
-
- /**
- * Supporting unsetting of magic attributes.
- *
- * @param string $name The name of the attribute or metadata.
- */
- function __unset($name) {
- if (array_key_exists($name, $this->attributes)) {
- $this->attributes[$name] = "";
- }
- else {
- $this->clearMetaData($name);
- }
- }
-
- /**
- * Set a piece of metadata.
- *
- * @param string $name Name of the metadata
- * @param mixed $value Value of the metadata
- * @param string $value_type Types supported: integer and string. Will auto-identify if not set
- * @param bool $multiple (does not support associative arrays)
- * @return bool
- */
- public function setMetaData($name, $value, $value_type = "", $multiple = false) {
- 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;
- }
- }
- }
-
- 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;
- }
-
- return true;
- }
- }
- }
-
- /**
- * Clear metadata.
- */
- public function clearMetaData($name = "") {
- if (empty($name)) {
- return clear_metadata($this->getGUID());
- } else {
- return remove_metadata($this->getGUID(),$name);
- }
- }
-
-
- /**
- * Get a piece of volatile (non-persisted) data on this entity
- */
- public function getVolatileData($name) {
- if (!is_array($this->volatile)) {
- $this->volatile = array();
- }
-
- if (array_key_exists($name, $this->volatile)) {
- return $this->volatile[$name];
- } else {
- return NULL;
- }
- }
-
-
- /**
- * Set a piece of volatile (non-persisted) data on this entity
- */
- public function setVolatileData($name, $value) {
- if (!is_array($this->volatile)) {
- $this->volatile = array();
- }
-
- $this->volatile[$name] = $value;
- }
-
-
- /**
- * Remove all entities associated with this entity
- *
- * @return true
- */
- public function clearRelationships() {
- remove_entity_relationships($this->getGUID());
- remove_entity_relationships($this->getGUID(),"",true);
- return true;
- }
-
- /**
- * Add a relationship.
- *
- * @param int $guid Relationship to link to.
- * @param string $relationship The type of relationship.
- * @return bool
- */
- public function addRelationship($guid, $relationship) {
- return add_entity_relationship($this->getGUID(), $relationship, $guid);
- }
-
- /**
- * Remove a relationship
- *
- * @param int $guid
- * @param str $relationship
- * @return bool
- */
- public function removeRelationship($guid, $relationship) {
- return remove_entity_relationship($this->getGUID(), $relationship, $guid);
- }
-
- /**
- * Adds a private setting to this entity.
- *
- * @param $name
- * @param $value
- * @return unknown_type
- */
- function setPrivateSetting($name, $value) {
- return set_private_setting($this->getGUID(), $name, $value);
- }
-
- /**
- * Gets private setting for this entity
- *
- * @param $name
- * @return unknown_type
- */
- function getPrivateSetting($name) {
- return get_private_setting($this->getGUID(), $name);
- }
-
- /**
- * Removes private setting for this entity.
- *
- * @param $name
- * @return unknown_type
- */
- function removePrivateSetting($name) {
- return remove_private_setting($this->getGUID(), $name);
- }
-
- /**
- * Adds an annotation to an entity. By default, the type is detected automatically; however,
- * it can also be set. Note that by default, annotations are private.
- *
- * @param string $name
- * @param mixed $value
- * @param int $access_id
- * @param int $owner_id
- * @param string $vartype
- */
- function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_id = 0, $vartype = "") {
- if ((int) $this->guid > 0) {
- return create_annotation($this->getGUID(), $name, $value, $vartype, $owner_id, $access_id);
- } else {
- $this->temp_annotations[$name] = $value;
- }
- return true;
- }
-
- /**
- * Get the annotations for an entity.
- *
- * @param string $name
- * @param int $limit
- * @param int $offset
- * @param string $order
- */
- function getAnnotations($name, $limit = 50, $offset = 0, $order="asc") {
- if ((int) ($this->guid) > 0) {
- return get_annotations($this->getGUID(), "", "", $name, "", 0, $limit, $offset, $order);
- } else {
- return $this->temp_annotations[$name];
- }
- }
-
- /**
- * Remove all annotations or all annotations for this entity.
- *
- * @param string $name
- */
- function clearAnnotations($name = "") {
- return clear_annotations($this->getGUID(), $name);
- }
-
- /**
- * Return the annotations for the entity.
- *
- * @param string $name The type of annotation.
- */
- function countAnnotations($name = "") {
- return count_annotations($this->getGUID(), "", "", $name);
- }
-
- /**
- * Get the average of an integer type annotation.
- *
- * @param string $name
- */
- function getAnnotationsAvg($name) {
- return get_annotations_avg($this->getGUID(), "", "", $name);
- }
-
- /**
- * Get the sum of integer type annotations of a given name.
- *
- * @param string $name
- */
- function getAnnotationsSum($name) {
- return get_annotations_sum($this->getGUID(), "", "", $name);
- }
-
- /**
- * Get the minimum of integer type annotations of given name.
- *
- * @param string $name
- */
- function getAnnotationsMin($name) {
- return get_annotations_min($this->getGUID(), "", "", $name);
- }
-
- /**
- * Get the maximum of integer type annotations of a given name.
- *
- * @param string $name
- */
- function getAnnotationsMax($name) {
- return get_annotations_max($this->getGUID(), "", "", $name);
- }
-
- /**
- * Gets an array of entities from a specific relationship type
- *
- * @param string $relationship Relationship type (eg "friends")
- * @param true|false $inverse Is this an inverse relationship?
- * @param int $limit Number of elements to return
- * @param int $offset Indexing offset
- * @return array|false An array of entities or false on failure
- */
- function getEntitiesFromRelationship($relationship, $inverse = false, $limit = 50, $offset = 0) {
- return elgg_get_entities_from_relationship(array(
- 'relationship' => $relationship,
- 'relationship_guid' => $this->getGUID(),
- 'inverse_relationship' => $inverse,
- 'limit' => $limit,
- 'offset' => $offset
- ));
- }
-
- /**
- * Gets the number of of entities from a specific relationship type
- *
- * @param string $relationship Relationship type (eg "friends")
- * @param bool $inverse_relationship
- * @return int|false The number of entities or false on failure
- */
- function countEntitiesFromRelationship($relationship, $inverse_relationship = FALSE) {
- return elgg_get_entities_from_relationship(array(
- 'relationship' => $relationship,
- 'relationship_guid' => $this->getGUID(),
- 'inverse_relationship' => $inverse_relationship,
- 'count' => TRUE
- ));
- }
-
- /**
- * Determines whether or not the specified user (by default the current one) can edit the entity
- *
- * @param int $user_guid The user GUID, optionally (defaults to the currently logged in user)
- * @return true|false
- */
- function canEdit($user_guid = 0) {
- return can_edit_entity($this->getGUID(), $user_guid);
- }
-
- /**
- * Determines whether or not the specified user (by default the current one) can edit metadata on the entity
- *
- * @param ElggMetadata $metadata The piece of metadata to specifically check
- * @param int $user_guid The user GUID, optionally (defaults to the currently logged in user)
- * @return true|false
- */
- function canEditMetadata($metadata = null, $user_guid = 0) {
- return can_edit_entity_metadata($this->getGUID(), $user_guid, $metadata);
- }
-
- /**
- * Returns whether the given user (or current user) has the ability to write to this container.
- *
- * @param int $user_guid The user.
- * @return bool
- */
- public function canWriteToContainer($user_guid = 0) {
- return can_write_to_container($user_guid, $this->getGUID());
- }
-
- /**
- * Obtain this entity's access ID
- *
- * @return int The access ID
- */
- public function getAccessID() {
- return $this->get('access_id');
- }
-
- /**
- * Obtain this entity's GUID
- *
- * @return int GUID
- */
- public function getGUID() {
- return $this->get('guid');
- }
-
- /**
- * Get the owner of this entity
- *
- * @return int The owner GUID
- */
- public function getOwner() {
- return $this->get('owner_guid');
- }
-
- /**
- * Returns the actual entity of the user who owns this entity, if any
- *
- * @return ElggEntity The owning user
- */
- public function getOwnerEntity() {
- return get_entity($this->get('owner_guid'));
- }
-
- /**
- * Gets the type of entity this is
- *
- * @return string Entity type
- */
- public function getType() {
- return $this->get('type');
- }
-
- /**
- * Returns the subtype of this entity
- *
- * @return string The entity subtype
- */
- public function getSubtype() {
- // If this object hasn't been saved, then return the subtype string.
- if (!((int) $this->guid > 0)) {
- return $this->get('subtype');
- }
-
- return get_subtype_from_id($this->get('subtype'));
- }
-
- /**
- * Gets the UNIX epoch time that this entity was created
- *
- * @return int UNIX epoch time
- */
- public function getTimeCreated() {
- return $this->get('time_created');
- }
-
- /**
- * Gets the UNIX epoch time that this entity was last updated
- *
- * @return int UNIX epoch time
- */
- public function getTimeUpdated() {
- return $this->get('time_updated');
- }
-
- /**
- * Gets the display URL for this entity
- *
- * @return string The URL
- */
- public function getURL() {
- if (!empty($this->url_override)) {
- return $this->url_override;
- }
- return get_entity_url($this->getGUID());
- }
-
- /**
- * Overrides the URL returned by getURL
- *
- * @param string $url The new item URL
- * @return string The URL
- */
- public function setURL($url) {
- $this->url_override = $url;
- return $url;
- }
-
- /**
- * Return a url for the entity's icon, trying multiple alternatives.
- *
- * @param string $size Either 'large','medium','small' or 'tiny'
- * @return string The url or false if no url could be worked out.
- */
- public function getIcon($size = 'medium') {
- if (isset($this->icon_override[$size])) {
- return $this->icon_override[$size];
- }
- return get_entity_icon_url($this, $size);
- }
-
- /**
- * Set an icon override for an icon and size.
- *
- * @param string $url The url of the icon.
- * @param string $size The size its for.
- * @return bool
- */
- public function setIcon($url, $size = 'medium') {
- $url = sanitise_string($url);
- $size = sanitise_string($size);
-
- if (!$this->icon_override) {
- $this->icon_override = array();
- }
- $this->icon_override[$size] = $url;
-
- return true;
- }
-
- /**
- * Tests to see whether the object has been fully loaded.
- *
- * @return bool
- */
- public function isFullyLoaded() {
- return ! ($this->attributes['tables_loaded'] < $this->attributes['tables_split']);
- }
-
- /**
- * Save generic attributes to the entities table.
- */
- public function save() {
- $guid = (int) $this->guid;
- if ($guid > 0) {
- cache_entity($this);
-
- return update_entity(
- $this->get('guid'),
- $this->get('owner_guid'),
- $this->get('access_id'),
- $this->get('container_guid')
- );
- } else {
- // Create a new entity (nb: using attribute array directly 'cos set function does something special!)
- $this->attributes['guid'] = create_entity($this->attributes['type'], $this->attributes['subtype'], $this->attributes['owner_guid'], $this->attributes['access_id'], $this->attributes['site_guid'], $this->attributes['container_guid']);
- if (!$this->attributes['guid']) {
- throw new IOException(elgg_echo('IOException:BaseEntitySaveFailed'));
- }
-
- // Save any unsaved metadata
- // @todo How to capture extra information (access id etc)
- if (sizeof($this->temp_metadata) > 0) {
- foreach($this->temp_metadata as $name => $value) {
- $this->$name = $value;
- unset($this->temp_metadata[$name]);
- }
- }
-
- // Save any unsaved annotations metadata.
- // @todo How to capture extra information (access id etc)
- if (sizeof($this->temp_annotations) > 0) {
- foreach($this->temp_annotations as $name => $value) {
- $this->annotate($name, $value);
- unset($this->temp_annotations[$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);
- }
-
- return $this->attributes['guid'];
- }
- }
-
- /**
- * Load the basic entity information and populate base attributes array.
- *
- * @param int $guid
- */
- protected function load($guid) {
- $row = get_entity_as_row($guid);
-
- if ($row) {
- // Create the array if necessary - all subclasses should test before creating
- if (!is_array($this->attributes)) {
- $this->attributes = array();
- }
-
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach($objarray as $key => $value) {
- $this->attributes[$key] = $value;
- }
-
- // Increment the portion counter
- if (!$this->isFullyLoaded()) {
- $this->attributes['tables_loaded']++;
- }
-
- // Cache object handle
- if ($this->attributes['guid']) {
- cache_entity($this);
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Disable this entity.
- *
- * @param string $reason Optional reason
- * @param bool $recursive Recursively disable all contained entities?
- */
- public function disable($reason = "", $recursive = true) {
- return disable_entity($this->get('guid'), $reason, $recursive);
- }
-
- /**
- * Re-enable this entity.
- */
- public function enable() {
- return enable_entity($this->get('guid'));
- }
-
- /**
- * Is this entity enabled?
- *
- * @return boolean
- */
- public function isEnabled() {
- if ($this->enabled == 'yes') {
- return true;
- }
-
- return false;
- }
-
- /**
- * Delete this entity.
- */
- public function delete() {
- return delete_entity($this->get('guid'));
- }
-
- // LOCATABLE INTERFACE /////////////////////////////////////////////////////////////
-
- /** Interface to set the location */
- public function setLocation($location) {
- $location = sanitise_string($location);
-
- $this->location = $location;
-
- return true;
- }
-
- /**
- * Set latitude and longitude tags for a given entity.
- *
- * @param float $lat
- * @param float $long
- */
- public function setLatLong($lat, $long) {
- $lat = sanitise_string($lat);
- $long = sanitise_string($long);
-
- $this->set('geo:lat', $lat);
- $this->set('geo:long', $long);
-
- return true;
- }
-
- /**
- * Get the contents of the ->geo:lat field.
- *
- */
- public function getLatitude() {
- return $this->get('geo:lat');
- }
-
- /**
- * Get the contents of the ->geo:lat field.
- *
- */
- public function getLongitude() {
- return $this->get('geo:long');
- }
-
- /**
- * Get the ->location metadata.
- *
- */
- public function getLocation() {
- return $this->get('location');
- }
-
- // NOTABLE INTERFACE ///////////////////////////////////////////////////////////////
-
- /**
- * Calendar functionality.
- * This function sets the time of an object on a calendar listing.
- *
- * @param int $hour If ommitted, now is assumed.
- * @param int $minute If ommitted, now is assumed.
- * @param int $second If ommitted, now is assumed.
- * @param int $day If ommitted, now is assumed.
- * @param int $month If ommitted, now is assumed.
- * @param int $year If ommitted, now is assumed.
- * @param int $duration Duration of event, remainder of the day is assumed.
- */
- public function setCalendarTimeAndDuration($hour = NULL, $minute = NULL, $second = NULL, $day = NULL, $month = NULL, $year = NULL, $duration = NULL) {
- $start = mktime($hour, $minute, $second, $month, $day, $year);
- $end = $start + abs($duration);
- if (!$duration) {
- $end = get_day_end($day,$month,$year);
- }
-
- $this->calendar_start = $start;
- $this->calendar_end = $end;
-
- return true;
- }
-
- /**
- * Return the start timestamp.
- */
- public function getCalendarStartTime() {
- return (int)$this->calendar_start;
- }
-
- /**
- * Return the end timestamp.
- */
- public function getCalendarEndTime() {
- return (int)$this->calendar_end;
- }
-
- // EXPORTABLE INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Return an array of fields which can be exported.
- */
- public function getExportableValues() {
- return array(
- 'guid',
- 'type',
- 'subtype',
- 'time_created',
- 'time_updated',
- 'container_guid',
- 'owner_guid',
- 'site_guid'
- );
- }
-
- /**
- * Export this class into an array of ODD Elements containing all necessary fields.
- * Override if you wish to return more information than can be found in $this->attributes (shouldn't happen)
- */
- public function export() {
- $tmp = array();
-
- // Generate uuid
- $uuid = guid_to_uuid($this->getGUID());
-
- // Create entity
- $odd = new ODDEntity(
- $uuid,
- $this->attributes['type'],
- get_subtype_from_id($this->attributes['subtype'])
- );
-
- $tmp[] = $odd;
-
- $exportable_values = $this->getExportableValues();
-
- // Now add its attributes
- foreach ($this->attributes as $k => $v) {
- $meta = NULL;
-
- 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 'time_created' : // Created = published
- $odd->setAttribute('published', date("r", $v));
- break;
-
- case 'site_guid' : // Container
- $k = 'site_uuid';
- $v = guid_to_uuid($v);
- $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
- break;
-
- case 'container_guid' : // Container
- $k = 'container_uuid';
- $v = guid_to_uuid($v);
- $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
- break;
-
- 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;
-
- default :
- $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
- }
-
- // set the time of any metadata created
- if ($meta) {
- $meta->setAttribute('published', date("r",$this->time_created));
- $tmp[] = $meta;
- }
- }
- }
-
- // Now we do something a bit special.
- /*
- * This provides a rendered view of the entity to foreign sites.
- */
-
- elgg_set_viewtype('default');
- $view = elgg_view_entity($this, true);
- elgg_set_viewtype();
-
- $tmp[] = new ODDMetaData($uuid . "volatile/renderedentity/", $uuid, 'renderedentity', $view , 'volatile');
-
- return $tmp;
- }
-
- // IMPORTABLE INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Import data from an parsed xml data array.
- *
- * @param array $data
- * @param int $version
- */
- public function import(ODD $data) {
- if (!($data instanceof ODDEntity)) {
- throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnexpectedODDClass'));
- }
-
- // Set type and subtype
- $this->attributes['type'] = $data->getAttribute('class');
- $this->attributes['subtype'] = $data->getAttribute('subclass');
-
- // Set owner
- $this->attributes['owner_guid'] = get_loggedin_userid(); // Import as belonging to importer.
-
- // Set time
- $this->attributes['time_created'] = strtotime($data->getAttribute('published'));
- $this->attributes['time_updated'] = time();
-
- return true;
- }
-
- // SYSTEM LOG INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Return an identification for the object for storage in the system log.
- * This id must be an integer.
- *
- * @return int
- */
- public function getSystemLogID() {
- return $this->getGUID();
- }
-
- /**
- * Return the class name of the object.
- */
- public function getClassName() {
- return get_class($this);
- }
-
- /**
- * For a given ID, return the object associated with it.
- * This is used by the river functionality primarily.
- * This is useful for checking access permissions etc on objects.
- */
- public function getObjectFromID($id) {
- return get_entity($id);
- }
-
- /**
- * Return the GUID of the owner of this object.
- */
- public function getObjectOwnerGUID() {
- return $this->owner_guid;
- }
-
- /**
- * Returns tags for this entity.
- *
- * @param array $tag_names Optionally restrict by tag metadata names.
- * @return array
- */
- public function getTags($tag_names = NULL) {
- global $CONFIG;
-
- if ($tag_names && !is_array($tag_names)) {
- $tag_names = array($tag_names);
- }
-
- $valid_tags = elgg_get_registered_tag_metadata_names();
- $entity_tags = array();
-
- foreach ($valid_tags as $tag_name) {
- if (is_array($tag_names) && !in_array($tag_name, $tag_names)) {
- continue;
- }
-
- if ($tags = $this->$tag_name) {
- // if a single tag, metadata returns a string.
- // if multiple tags, metadata returns an array.
- if (is_array($tags)) {
- $entity_tags = array_merge($entity_tags, $tags);
- } else {
- $entity_tags[] = $tags;
- }
- }
- }
-
- return $entity_tags;
- }
-
- // ITERATOR INTERFACE //////////////////////////////////////////////////////////////
- /*
- * This lets an entity's attributes be displayed using foreach as a normal array.
- * Example: http://www.sitepoint.com/print/php5-standard-library
- */
-
- private $valid = FALSE;
-
- function rewind() {
- $this->valid = (FALSE !== reset($this->attributes));
- }
-
- function current() {
- return current($this->attributes);
- }
-
- function key() {
- return key($this->attributes);
- }
-
- function next() {
- $this->valid = (FALSE !== next($this->attributes));
- }
-
- function valid() {
- return $this->valid;
- }
-
- // ARRAY ACCESS INTERFACE //////////////////////////////////////////////////////////
- /*
- * This lets an entity's attributes be accessed like an associative array.
- * Example: http://www.sitepoint.com/print/php5-standard-library
- */
-
- function offsetSet($key, $value) {
- if ( array_key_exists($key, $this->attributes) ) {
- $this->attributes[$key] = $value;
- }
- }
-
- function offsetGet($key) {
- if ( array_key_exists($key, $this->attributes) ) {
- return $this->attributes[$key];
- }
- }
-
- function offsetUnset($key) {
- if ( array_key_exists($key, $this->attributes) ) {
- $this->attributes[$key] = ""; // Full unsetting is dangerious for our objects
- }
- }
-
- function offsetExists($offset) {
- return array_key_exists($offset, $this->attributes);
- }
-}
\ No newline at end of file +<?php +/** + * The parent class for all Elgg Entities. + * + * An ElggEntity is one of the basic data models in Elgg. It is the primary + * means of storing and retrieving data from the database. An ElggEntity + * represents one row of the entities table. + * + * The ElggEntity class handles CRUD operations for the entities table. + * ElggEntity should always be extended by another class to handle CRUD + * operations on the type-specific table. + * + * ElggEntity uses magic methods for get and set, so any property that isn't + * declared will be assumed to be metadata and written to the database + * as metadata on the object. All children classes must declare which + * properties are columns of the type table or they will be assumed + * to be metadata. See ElggObject::initialise_entities() for examples. + * + * Core supports 4 types of entities: ElggObject, ElggUser, ElggGroup, and + * ElggSite. + * + * @tip Most plugin authors will want to extend the ElggObject class + * instead of this class. + * + * @package Elgg.Core + * @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 + Locatable, // Geocoding interface + Importable // Allow import of data +{ + + /** + * If set, overrides the value of getURL() + */ + protected $url_override; + + /** + * Icon override, overrides the value of getIcon(). + */ + protected $icon_override; + + /** + * Holds metadata until entity is saved. Once the entity is saved, + * metadata are written immediately to the database. + */ + protected $temp_metadata = array(); + + /** + * Holds annotations until entity is saved. Once the entity is saved, + * annotations are written immediately to the database. + */ + protected $temp_annotations = array(); + + /** + * Holds private settings until entity is saved. Once the entity is saved, + * private settings are written immediately to the database. + */ + protected $temp_private_settings = array(); + + /** + * Volatile data structure for this object, allows for storage of data + * in-memory that isn't sync'd back to the metadata table. + */ + protected $volatile = array(); + + /** + * Initialize the attributes array. + * + * This is vital to distinguish between metadata and base parameters. + * + * @return void + */ + protected function initializeAttributes() { + parent::initializeAttributes(); + + $this->attributes['guid'] = NULL; + $this->attributes['type'] = NULL; + $this->attributes['subtype'] = NULL; + + $this->attributes['owner_guid'] = elgg_get_logged_in_user_guid(); + $this->attributes['container_guid'] = elgg_get_logged_in_user_guid(); + + $this->attributes['site_guid'] = NULL; + $this->attributes['access_id'] = ACCESS_PRIVATE; + $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 + /* Problem: To speed things up, some objects are split over several tables, + * this means that it requires n number of database reads to fully populate + * an entity. This causes problems for caching and create events + * since it is not possible to tell whether a subclassed entity is complete. + * + * Solution: We have two counters, one 'tables_split' which tells whatever is + * interested how many tables are going to need to be searched in order to fully + * populate this object, and 'tables_loaded' which is how many have been + * loaded thus far. + * + * If the two are the same then this object is complete. + * + * Use: isFullyLoaded() to check + */ + $this->attributes['tables_split'] = 1; + $this->attributes['tables_loaded'] = 0; + } + + /** + * Clone an entity + * + * Resets the guid so that the entity can be saved as a distinct entity from + * the original. Creation time will be set when this new entity is saved. + * The owner and container guids come from the original entity. The clone + * method copies metadata but does not copy annotations or private settings. + * + * @note metadata will have its owner and access id set when the entity is saved + * and it will be the same as that of the entity. + * + * @return void + */ + public function __clone() { + $orig_entity = get_entity($this->guid); + if (!$orig_entity) { + elgg_log("Failed to clone entity with GUID $this->guid", "ERROR"); + return; + } + + $metadata_array = elgg_get_metadata(array( + 'guid' => $this->guid, + 'limit' => 0 + )); + + $this->attributes['guid'] = ""; + + $this->attributes['subtype'] = $orig_entity->getSubtype(); + + // copy metadata over to new entity - slightly convoluted due to + // handling of metadata arrays + if (is_array($metadata_array)) { + // create list of metadata names + $metadata_names = array(); + foreach ($metadata_array as $metadata) { + $metadata_names[] = $metadata['name']; + } + // arrays are stored with multiple enties per name + $metadata_names = array_unique($metadata_names); + + // move the metadata over + foreach ($metadata_names as $name) { + $this->set($name, $orig_entity->$name); + } + } + } + + /** + * Return the value of a property. + * + * If $name is defined in $this->attributes that value is returned, otherwise it will + * pull from the entity's metadata. + * + * Q: Why are we not using __get overload here? + * A: Because overload operators cause problems during subclassing, so we put the code here and + * create overloads in subclasses. + * + * @todo What problems are these? + * + * @warning Subtype is returned as an id rather than the subtype string. Use getSubtype() + * to get the subtype string. + * + * @param string $name Name + * + * @return mixed Returns the value of a given value, or null. + */ + public function get($name) { + // See if its in our base attributes + if (array_key_exists($name, $this->attributes)) { + return $this->attributes[$name]; + } + + // No, so see if its in the meta data for this entity + $meta = $this->getMetaData($name); + + // getMetaData returns NULL if $name is not found + return $meta; + } + + /** + * Sets the value of a property. + * + * 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. + * + * Q: Why are we not using __set overload here? + * A: Because overload operators cause problems during subclassing, so we put the code here and + * create overloads in subclasses. + * + * @todo What problems? + * + * @param string $name Name + * @param mixed $value Value + * + * @return bool + */ + public function set($name, $value) { + if (array_key_exists($name, $this->attributes)) { + // Certain properties should not be manually changed! + switch ($name) { + case 'guid': + case 'time_updated': + case 'last_action': + return FALSE; + break; + default: + $this->attributes[$name] = $value; + break; + } + } else { + return $this->setMetaData($name, $value); + } + + return TRUE; + } + + /** + * Return the value of a piece of metadata. + * + * @param string $name Name + * + * @return mixed The value, or NULL if not found. + */ + public function getMetaData($name) { + $guid = $this->getGUID(); + + if (! $guid) { + if (isset($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)) { + $value = $md->value; + } elseif (count($md) == 1) { + $value = $md[0]->value; + } else if ($md && is_array($md)) { + $value = metadata_array_to_values($md); + } + + $cache->save($guid, $name, $value); + + return $value; + } + + /** + * Unset a property from metadata or attribute. + * + * @warning If you use this to unset an attribute, you must save the object! + * + * @param string $name The name of the attribute or metadata. + * + * @return void + */ + function __unset($name) { + if (array_key_exists($name, $this->attributes)) { + $this->attributes[$name] = ""; + } else { + $this->deleteMetadata($name); + } + } + + /** + * Set a piece of metadata. + * + * 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 (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 = null, $multiple = false) { + + // normalize value to an array that we will loop over + // remove indexes if value already an array. + if (is_array($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); + } + + // 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 $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; + } + + /** + * Remove metadata + * + * @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 = '') { + 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. + * + * @param string $name The name of the volatile data + * + * @return mixed The value or NULL if not found. + */ + public function getVolatileData($name) { + if (!is_array($this->volatile)) { + $this->volatile = array(); + } + + if (array_key_exists($name, $this->volatile)) { + return $this->volatile[$name]; + } else { + return NULL; + } + } + + /** + * Set a piece of volatile (non-persisted) data on this entity + * + * @param string $name Name + * @param mixed $value Value + * + * @return void + */ + public function setVolatileData($name, $value) { + if (!is_array($this->volatile)) { + $this->volatile = array(); + } + + $this->volatile[$name] = $value; + } + + /** + * Remove all relationships to and from this entity. + * + * @return true + * @todo This should actually return if it worked. + * @see ElggEntity::addRelationship() + * @see ElggEntity::removeRelationship() + */ + 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." + * + * @param int $guid Entity to link to. + * @param string $relationship The type of relationship. + * + * @return bool + * @see ElggEntity::removeRelationship() + * @see ElggEntity::clearRelationships() + */ + public function addRelationship($guid, $relationship) { + return add_entity_relationship($this->getGUID(), $relationship, $guid); + } + + /** + * Remove a relationship + * + * @param int $guid GUID of the entity to make a relationship with + * @param str $relationship Name of relationship + * + * @return bool + * @see ElggEntity::addRelationship() + * @see ElggEntity::clearRelationships() + */ + public function removeRelationship($guid, $relationship) { + return remove_entity_relationship($this->getGUID(), $relationship, $guid); + } + + /** + * Adds a private setting to this entity. + * + * Private settings are similar to metadata but will not + * be searched and there are fewer helper functions for them. + * + * @param string $name Name of private setting + * @param mixed $value Value of private setting + * + * @return bool + */ + function setPrivateSetting($name, $value) { + if ((int) $this->guid > 0) { + return set_private_setting($this->getGUID(), $name, $value); + } else { + $this->temp_private_settings[$name] = $value; + return true; + } + } + + /** + * Returns a private setting value + * + * @param string $name Name of the private setting + * + * @return mixed + */ + function getPrivateSetting($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; + } + + /** + * Removes private setting + * + * @param string $name Name of the private setting + * + * @return bool + */ + function removePrivateSetting($name) { + return remove_private_setting($this->getGUID(), $name); + } + + /** + * 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 + * @param int $owner_id GUID of the annotation owner + * @param string $vartype The type of annotation value + * + * @return bool + */ + function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_id = 0, $vartype = "") { + if ((int) $this->guid > 0) { + return create_annotation($this->getGUID(), $name, $value, $vartype, $owner_id, $access_id); + } else { + $this->temp_annotations[$name] = $value; + } + return true; + } + + /** + * Returns an array of annotations. + * + * @param string $name Annotation name + * @param int $limit Limit + * @param int $offset Offset + * @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) { + + $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 array(); + } + } + + /** + * Remove an annotation or all annotations for this entity. + * + * @warning Calling this method with no or an empty argument will remove + * all annotations on the entity. + * + * @param string $name Annotation name + * @return bool + * @deprecated 1.8 Use ->deleteAnnotations() + */ + function clearAnnotations($name = "") { + elgg_deprecated_notice('ElggEntity->clearAnnotations() is deprecated by ->deleteAnnotations()', 1.8); + return $this->deleteAnnotations($name); + } + + /** + * Count annotations. + * + * @param string $name The type of annotation. + * + * @return int + */ + function countAnnotations($name = "") { + return $this->getAnnotationCalculation($name, 'count'); + } + + /** + * Get the average of an integer type annotation. + * + * @param string $name Annotation name + * + * @return int + */ + function getAnnotationsAvg($name) { + return $this->getAnnotationCalculation($name, 'avg'); + } + + /** + * Get the sum of integer type annotations of a given name. + * + * @param string $name Annotation name + * + * @return int + */ + function getAnnotationsSum($name) { + return $this->getAnnotationCalculation($name, 'sum'); + } + + /** + * Get the minimum of integer type annotations of given name. + * + * @param string $name Annotation name + * + * @return int + */ + function getAnnotationsMin($name) { + return $this->getAnnotationCalculation($name, 'min'); + } + + /** + * Get the maximum of integer type annotations of a given name. + * + * @param string $name Annotation name + * + * @return int + */ + function getAnnotationsMax($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'); + } + } + + /** + * Gets an array of entities with a relationship to this entity. + * + * @param string $relationship Relationship type (eg "friends") + * @param bool $inverse Is this an inverse relationship? + * @param int $limit Number of elements to return + * @param int $offset Indexing offset + * + * @return array|false An array of entities or false on failure + */ + function getEntitiesFromRelationship($relationship, $inverse = false, $limit = 50, $offset = 0) { + return elgg_get_entities_from_relationship(array( + 'relationship' => $relationship, + 'relationship_guid' => $this->getGUID(), + 'inverse_relationship' => $inverse, + 'limit' => $limit, + 'offset' => $offset + )); + } + + /** + * Gets the number of of entities from a specific relationship type + * + * @param string $relationship Relationship type (eg "friends") + * @param bool $inverse_relationship Invert relationship + * + * @return int|false The number of entities or false on failure + */ + function countEntitiesFromRelationship($relationship, $inverse_relationship = FALSE) { + return elgg_get_entities_from_relationship(array( + 'relationship' => $relationship, + 'relationship_guid' => $this->getGUID(), + 'inverse_relationship' => $inverse_relationship, + 'count' => TRUE + )); + } + + /** + * Can a user edit this entity. + * + * @param int $user_guid The user GUID, optionally (default: logged in user) + * + * @return bool + */ + function canEdit($user_guid = 0) { + return can_edit_entity($this->getGUID(), $user_guid); + } + + /** + * Can a user edit metadata on this entity + * + * @param ElggMetadata $metadata The piece of metadata to specifically check + * @param int $user_guid The user GUID, optionally (default: logged in user) + * + * @return bool + */ + function canEditMetadata($metadata = null, $user_guid = 0) { + return can_edit_entity_metadata($this->getGUID(), $user_guid, $metadata); + } + + /** + * 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? + * + * @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 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); + } + + /** + * Returns the access_id. + * + * @return int The access ID + */ + public function getAccessID() { + return $this->get('access_id'); + } + + /** + * Returns the guid. + * + * @return int|null GUID + */ + public function getGUID() { + return $this->get('guid'); + } + + /** + * Returns the entity type + * + * @return string Entity type + */ + public function getType() { + return $this->get('type'); + } + + /** + * Returns the entity subtype string + * + * @note This returns a string. If you want the id, use ElggEntity::subtype. + * + * @return string The entity subtype + */ + public function getSubtype() { + // If this object hasn't been saved, then return the subtype string. + if (!((int) $this->guid > 0)) { + return $this->get('subtype'); + } + + return get_subtype_from_id($this->get('subtype')); + } + + /** + * 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() { + elgg_deprecated_notice("ElggEntity::getOwner deprecated for ElggEntity::getOwnerGUID", 1.8); + return $this->getOwnerGUID(); + } + + /** + * Gets the ElggEntity that owns this entity. + * + * @return ElggEntity The owning entity + */ + public function getOwnerEntity() { + return get_entity($this->owner_guid); + } + + /** + * Set the container for this object. + * + * @param int $container_guid The ID of the container. + * + * @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); + } + + /** + * 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'); + } + + /** + * Get the container entity for this object. + * + * @return ElggEntity + * @since 1.8.0 + */ + public function getContainerEntity() { + return get_entity($this->getContainerGUID()); + } + + /** + * Returns the UNIX epoch time that this entity was last updated + * + * @return int UNIX epoch time + */ + public function getTimeUpdated() { + return $this->get('time_updated'); + } + + /** + * Returns the URL for this entity + * + * @return string The URL + * @see register_entity_url_handler() + * @see ElggEntity::setURL() + */ + public function getURL() { + if (!empty($this->url_override)) { + return $this->url_override; + } + return get_entity_url($this->getGUID()); + } + + /** + * Overrides the URL returned by getURL() + * + * @warning This override exists only for the life of the object. + * + * @param string $url The new item URL + * + * @return string The URL + */ + public function setURL($url) { + $this->url_override = $url; + return $url; + } + + /** + * 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. + * @deprecated Use getIconURL() + */ + public function getIcon($size = 'medium') { + elgg_deprecated_notice("getIcon() deprecated by getIconURL()", 1.8); + return $this->getIconURL($size); + } + + /** + * Set an icon override for an icon and size. + * + * @warning This override exists only for the life of the object. + * + * @param string $url The url of the icon. + * @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); + + if (!$this->icon_override) { + $this->icon_override = array(); + } + $this->icon_override[$size] = $url; + + return true; + } + + /** + * Tests to see whether the object has been fully loaded. + * + * @return bool + */ + public function isFullyLoaded() { + return ! ($this->attributes['tables_loaded'] < $this->attributes['tables_split']); + } + + /** + * Save an entity. + * + * @return bool|int + * @throws IOException + */ + public function save() { + $guid = $this->getGUID(); + if ($guid > 0) { + + // 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('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!) + $this->attributes['guid'] = create_entity($this->attributes['type'], + $this->attributes['subtype'], $this->attributes['owner_guid'], + $this->attributes['access_id'], $this->attributes['site_guid'], + $this->attributes['container_guid']); + + if (!$this->attributes['guid']) { + throw new IOException(elgg_echo('IOException:BaseEntitySaveFailed')); + } + + // Save any unsaved metadata + // @todo How to capture extra information (access id etc) + if (sizeof($this->temp_metadata) > 0) { + foreach ($this->temp_metadata as $name => $value) { + $this->$name = $value; + unset($this->temp_metadata[$name]); + } + } + + // Save any unsaved annotations. + if (sizeof($this->temp_annotations) > 0) { + foreach ($this->temp_annotations as $name => $value) { + $this->annotate($name, $value); + unset($this->temp_annotations[$name]); + } + } + + // 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']); + + _elgg_cache_entity($this); + + return $this->attributes['guid']; + } + } + + /** + * Loads attributes from the entities table into the object. + * + * @param mixed $guid GUID of entity or stdClass object from entities table + * + * @return bool + */ + protected function load($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 + if (!is_array($this->attributes)) { + $this->attributes = array(); + } + + // Now put these into the attributes array as core values + $objarray = (array) $row; + foreach ($objarray as $key => $value) { + $this->attributes[$key] = $value; + } + + // Increment the portion counter + if (!$this->isFullyLoaded()) { + $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']) { + _elgg_cache_entity($this); + } + + return true; + } + + return false; + } + + /** + * Disable this entity. + * + * Disabled entities are not returned by getter functions. + * To enable an entity, use {@link enable_entity()}. + * + * Recursively disabling an entity will disable all entities + * owned or contained by the parent entity. + * + * @internal Disabling an entity sets the 'enabled' column to 'no'. + * + * @param string $reason Optional reason + * @param bool $recursive Recursively disable all contained entities? + * + * @return bool + * @see enable_entity() + * @see ElggEntity::enable() + */ + public function disable($reason = "", $recursive = true) { + if ($r = disable_entity($this->get('guid'), $reason, $recursive)) { + $this->attributes['enabled'] = 'no'; + } + + return $r; + } + + /** + * Enable an entity + * + * @warning Disabled entities can't be loaded unless + * {@link access_show_hidden_entities(true)} has been called. + * + * @see enable_entity() + * @see access_show_hiden_entities() + * @return bool + */ + public function enable() { + if ($r = enable_entity($this->get('guid'))) { + $this->attributes['enabled'] = 'yes'; + } + + return $r; + } + + /** + * Is this entity enabled? + * + * @return boolean + */ + public function isEnabled() { + if ($this->enabled == 'yes') { + return true; + } + + return false; + } + + /** + * Delete this entity. + * + * @param bool $recursive Whether to delete all the entities contained by this entity + * + * @return bool + */ + public function delete($recursive = true) { + return delete_entity($this->get('guid'), $recursive); + } + + /* + * LOCATABLE INTERFACE + */ + + /** + * 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 bool + */ + public function setLocation($location) { + $this->location = $location; + return true; + } + + /** + * Set latitude and longitude metadata tags for a given entity. + * + * @param float $lat Latitude + * @param float $long Longitude + * + * @return bool + * @todo Unimplemented + */ + public function setLatLong($lat, $long) { + $this->set('geo:lat', $lat); + $this->set('geo:long', $long); + + return true; + } + + /** + * Return the entity's latitude. + * + * @return float + * @todo Unimplemented + */ + public function getLatitude() { + return (float)$this->get('geo:lat'); + } + + /** + * Return the entity's longitude + * + * @return float + */ + public function getLongitude() { + return (float)$this->get('geo:long'); + } + + /* + * NOTABLE INTERFACE + */ + + /** + * Set the time and duration of an object + * + * @param int $hour If ommitted, now is assumed. + * @param int $minute If ommitted, now is assumed. + * @param int $second If ommitted, now is assumed. + * @param int $day If ommitted, now is assumed. + * @param int $month If ommitted, now is assumed. + * @param int $year If ommitted, now is assumed. + * @param int $duration Duration of event, remainder of the day is assumed. + * + * @return true + * @todo Unimplemented + */ + public function setCalendarTimeAndDuration($hour = NULL, $minute = NULL, $second = NULL, + $day = NULL, $month = NULL, $year = NULL, $duration = NULL) { + + $start = mktime($hour, $minute, $second, $month, $day, $year); + $end = $start + abs($duration); + if (!$duration) { + $end = get_day_end($day, $month, $year); + } + + $this->calendar_start = $start; + $this->calendar_end = $end; + + return true; + } + + /** + * Returns the start timestamp. + * + * @return int + * @todo Unimplemented + */ + public function getCalendarStartTime() { + return (int)$this->calendar_start; + } + + /** + * Returns the end timestamp. + * + * @todo Unimplemented + * + * @return int + */ + public function getCalendarEndTime() { + return (int)$this->calendar_end; + } + + /* + * EXPORTABLE INTERFACE + */ + + /** + * Returns an array of fields which can be exported. + * + * @return array + */ + public function getExportableValues() { + return array( + 'guid', + 'type', + 'subtype', + 'time_created', + 'time_updated', + 'container_guid', + 'owner_guid', + 'site_guid' + ); + } + + /** + * Export this class into an array of ODD Elements containing all necessary fields. + * Override if you wish to return more information than can be found in + * $this->attributes (shouldn't happen) + * + * @return array + */ + public function export() { + $tmp = array(); + + // Generate uuid + $uuid = guid_to_uuid($this->getGUID()); + + // Create entity + $odd = new ODDEntity( + $uuid, + $this->attributes['type'], + get_subtype_from_id($this->attributes['subtype']) + ); + + $tmp[] = $odd; + + $exportable_values = $this->getExportableValues(); + + // Now add its attributes + foreach ($this->attributes as $k => $v) { + $meta = NULL; + + 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 'time_created': // Created = published + $odd->setAttribute('published', date("r", $v)); + break; + + case 'site_guid': // Container + $k = 'site_uuid'; + $v = guid_to_uuid($v); + $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); + break; + + case 'container_guid': // Container + $k = 'container_uuid'; + $v = guid_to_uuid($v); + $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); + break; + + 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; + + default: + $meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v); + } + + // set the time of any metadata created + if ($meta) { + $meta->setAttribute('published', date("r", $this->time_created)); + $tmp[] = $meta; + } + } + } + + // Now we do something a bit special. + /* + * This provides a rendered view of the entity to foreign sites. + */ + + elgg_set_viewtype('default'); + $view = elgg_view_entity($this, array('full_view' => true)); + elgg_set_viewtype(); + + $tmp[] = new ODDMetaData($uuid . "volatile/renderedentity/", $uuid, + 'renderedentity', $view, 'volatile'); + + return $tmp; + } + + /* + * IMPORTABLE INTERFACE + */ + + /** + * Import data from an parsed ODD xml data array. + * + * @param ODD $data XML data + * + * @return true + * + * @throws InvalidParameterException + */ + public function import(ODD $data) { + if (!($data instanceof ODDEntity)) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnexpectedODDClass')); + } + + // Set type and subtype + $this->attributes['type'] = $data->getAttribute('class'); + $this->attributes['subtype'] = $data->getAttribute('subclass'); + + // Set owner + $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')); + $this->attributes['time_updated'] = time(); + + return true; + } + + /* + * SYSTEM LOG INTERFACE + */ + + /** + * Return an identification for the object for storage in the system log. + * This id must be an integer. + * + * @return int + */ + public function getSystemLogID() { + return $this->getGUID(); + } + + /** + * For a given ID, return the object associated with it. + * This is used by the river functionality primarily. + * + * This is useful for checking access permissions etc on objects. + * + * @param int $id GUID. + * + * @todo How is this any different or more useful than get_entity($guid) + * or new ElggEntity($guid)? + * + * @return int GUID + */ + public function getObjectFromID($id) { + return get_entity($id); + } + + /** + * Returns tags for this entity. + * + * @warning Tags must be registered by {@link elgg_register_tag_metadata_name()}. + * + * @param array $tag_names Optionally restrict by tag metadata names. + * + * @return array + */ + public function getTags($tag_names = NULL) { + if ($tag_names && !is_array($tag_names)) { + $tag_names = array($tag_names); + } + + $valid_tags = elgg_get_registered_tag_metadata_names(); + $entity_tags = array(); + + foreach ($valid_tags as $tag_name) { + if (is_array($tag_names) && !in_array($tag_name, $tag_names)) { + continue; + } + + if ($tags = $this->$tag_name) { + // if a single tag, metadata returns a string. + // if multiple tags, metadata returns an array. + if (is_array($tags)) { + $entity_tags = array_merge($entity_tags, $tags); + } else { + $entity_tags[] = $tags; + } + } + } + + return $entity_tags; + } +} diff --git a/engine/classes/ElggExtender.php b/engine/classes/ElggExtender.php index ec7a21f35..25aba354f 100644 --- a/engine/classes/ElggExtender.php +++ b/engine/classes/ElggExtender.php @@ -1,252 +1,214 @@ -<?php
-
-/**
- * ElggExtender
- *
- * @author Curverider Ltd
- * @package Elgg
- * @subpackage Core
- */
-abstract class ElggExtender implements
- Exportable,
- Loggable, // Can events related to this object class be logged
- Iterator, // Override foreach behaviour
- ArrayAccess // Override for array access
-{
- /**
- * This contains the site's main properties (id, etc)
- * @var array
- */
- protected $attributes;
-
- /**
- * Get an attribute
- *
- * @param string $name
- * @return mixed
- */
- protected function get($name) {
- if (isset($this->attributes[$name])) {
- // Sanitise value if necessary
- if ($name=='value') {
- switch ($this->attributes['value_type']) {
- case 'integer' :
- return (int)$this->attributes['value'];
-
- //case 'tag' :
- //case 'file' :
- case 'text' :
- return ($this->attributes['value']);
-
- default :
- throw new InstallationException(sprintf(elgg_echo('InstallationException:TypeNotSupported'), $this->attributes['value_type']));
- }
- }
-
- return $this->attributes[$name];
- }
- return null;
- }
-
- /**
- * Set an attribute
- *
- * @param string $name
- * @param mixed $value
- * @param string $value_type
- * @return boolean
- */
- protected function set($name, $value, $value_type = "") {
- $this->attributes[$name] = $value;
- if ($name == 'value') {
- $this->attributes['value_type'] = detect_extender_valuetype($value, $value_type);
- }
-
- return true;
- }
-
- /**
- * Return the owner of this annotation.
- *
- * @return mixed
- */
- public function getOwner() {
- return $this->owner_guid;
- }
-
- /**
- * Return the owner entity
- *
- * @return mixed
- * @since 1.7.0
- */
- public function getOwnerEntity() {
- return get_user($this->owner_guid);
- }
-
- /**
- * Returns the entity this is attached to
- *
- * @return ElggEntity The enttiy
- */
- public function getEntity() {
- return get_entity($this->entity_guid);
- }
-
- /**
- * Save this data to the appropriate database table.
- */
- abstract public function save();
-
- /**
- * Delete this data.
- */
- abstract public function delete();
-
- /**
- * Determines whether or not the specified user can edit this
- *
- * @param int $user_guid The GUID of the user (defaults to currently logged in user)
- * @return true|false
- */
- public function canEdit($user_guid = 0) {
- return can_edit_extender($this->id,$this->type,$user_guid);
- }
-
- /**
- * Return a url for this extender.
- *
- * @return string
- */
- public abstract function getURL();
-
- // EXPORTABLE INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Return an array of fields which can be exported.
- */
- public function getExportableValues() {
- return array(
- 'id',
- 'entity_guid',
- 'name',
- 'value',
- 'value_type',
- 'owner_guid',
- 'type',
- );
- }
-
- /**
- * Export this object
- *
- * @return array
- */
- public function export() {
- $uuid = get_uuid_from_object($this);
-
- $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));
-
- return $meta;
- }
-
- // SYSTEM LOG INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Return an identification for the object for storage in the system log.
- * This id must be an integer.
- *
- * @return int
- */
- public function getSystemLogID() {
- return $this->id;
- }
-
- /**
- * Return the class name of the object.
- */
- public function getClassName() {
- return get_class($this);
- }
-
- /**
- * Return the GUID of the owner of this object.
- */
- public function getObjectOwnerGUID() {
- return $this->owner_guid;
- }
-
- /**
- * Return a type of the object - eg. object, group, user, relationship, metadata, annotation etc
- */
- public function getType() {
- return $this->type;
- }
-
- /**
- * Return a subtype. For metadata & annotations this is the 'name' and
- * for relationship this is the relationship type.
- */
- public function getSubtype() {
- return $this->name;
- }
-
-
- // ITERATOR INTERFACE //////////////////////////////////////////////////////////////
- /*
- * This lets an entity's attributes be displayed using foreach as a normal array.
- * Example: http://www.sitepoint.com/print/php5-standard-library
- */
-
- private $valid = FALSE;
-
- function rewind() {
- $this->valid = (FALSE !== reset($this->attributes));
- }
-
- function current() {
- return current($this->attributes);
- }
-
- function key() {
- return key($this->attributes);
- }
-
- function next() {
- $this->valid = (FALSE !== next($this->attributes));
- }
-
- function valid() {
- return $this->valid;
- }
-
- // ARRAY ACCESS INTERFACE //////////////////////////////////////////////////////////
- /*
- * This lets an entity's attributes be accessed like an associative array.
- * Example: http://www.sitepoint.com/print/php5-standard-library
- */
-
- function offsetSet($key, $value) {
- if ( array_key_exists($key, $this->attributes) ) {
- $this->attributes[$key] = $value;
- }
- }
-
- function offsetGet($key) {
- if ( array_key_exists($key, $this->attributes) ) {
- return $this->attributes[$key];
- }
- }
-
- function offsetUnset($key) {
- if ( array_key_exists($key, $this->attributes) ) {
- // Full unsetting is dangerious for our objects
- $this->attributes[$key] = "";
- }
- }
-
- function offsetExists($offset) {
- return array_key_exists($offset, $this->attributes);
- }
-}
+<?php +/** + * The base class for ElggEntity extenders. + * + * Extenders allow you to attach extended information to an + * ElggEntity. Core supports two: ElggAnnotation and ElggMetadata. + * + * Saving the extender data to database is handled by the child class. + * + * @tip Plugin authors would probably want to extend either ElggAnnotation + * or ElggMetadata instead of this class. + * + * @package Elgg.Core + * @subpackage DataModel.Extender + * @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 { + + /** + * (non-PHPdoc) + * + * @see ElggData::initializeAttributes() + * + * @return void + */ + protected function initializeAttributes() { + parent::initializeAttributes(); + + $this->attributes['type'] = NULL; + } + + /** + * Returns an attribute + * + * @param string $name Name + * + * @return mixed + */ + protected function get($name) { + if (array_key_exists($name, $this->attributes)) { + // Sanitise value if necessary + if ($name == 'value') { + switch ($this->attributes['value_type']) { + case 'integer' : + return (int)$this->attributes['value']; + break; + + //case 'tag' : + //case 'file' : + case 'text' : + return ($this->attributes['value']); + break; + + default : + $msg = elgg_echo('InstallationException:TypeNotSupported', array( + $this->attributes['value_type'])); + + throw new InstallationException($msg); + break; + } + } + + return $this->attributes[$name]; + } + return null; + } + + /** + * Set an attribute + * + * @param string $name Name + * @param mixed $value Value + * @param string $value_type Value type + * + * @return boolean + */ + protected function set($name, $value, $value_type = "") { + $this->attributes[$name] = $value; + if ($name == 'value') { + $this->attributes['value_type'] = detect_extender_valuetype($value, $value_type); + } + + return true; + } + + /** + * 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() { + elgg_deprecated_notice("ElggExtender::getOwner deprecated for ElggExtender::getOwnerGUID", 1.8); + return $this->getOwnerGUID(); + } + + /** + * Get the entity that owns this extender + * + * @return ElggEntity + */ + public function getOwnerEntity() { + return get_entity($this->owner_guid); + } + + /** + * Get the entity this describes. + * + * @return ElggEntity The entity + */ + public function getEntity() { + return get_entity($this->entity_guid); + } + + /** + * Returns if a user can edit this extended data. + * + * @param int $user_guid The GUID of the user (defaults to currently logged in user) + * + * @return bool + */ + public function canEdit($user_guid = 0) { + return can_edit_extender($this->id, $this->type, $user_guid); + } + + /* + * EXPORTABLE INTERFACE + */ + + /** + * Return an array of fields which can be exported. + * + * @return array + */ + public function getExportableValues() { + return array( + 'id', + 'entity_guid', + 'name', + 'value', + 'value_type', + 'owner_guid', + 'type', + ); + } + + /** + * Export this object + * + * @return array + */ + public function export() { + $uuid = get_uuid_from_object($this); + + $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)); + + return $meta; + } + + /* + * SYSTEM LOG INTERFACE + */ + + /** + * Return an identification for the object for storage in the system log. + * This id must be an integer. + * + * @return int + */ + public function getSystemLogID() { + return $this->id; + } + + /** + * Return a type of extension. + * + * @return string + */ + public function getType() { + return $this->type; + } + + /** + * Return a subtype. For metadata & annotations this is the 'name' and + * for relationship this is the relationship type. + * + * @return string + */ + public function getSubtype() { + return $this->name; + } + +} diff --git a/engine/classes/ElggFile.php b/engine/classes/ElggFile.php index 68ed2f2f2..23080834b 100644 --- a/engine/classes/ElggFile.php +++ b/engine/classes/ElggFile.php @@ -1,21 +1,24 @@ <?php /** - * @class ElggFile * This class represents a physical file. * - * Usage: - * Create a new ElggFile object and specify a filename, and optionally a FileStore (if one isn't specified - * then the default is assumed. + * Create a new ElggFile object and specify a filename, and optionally a + * FileStore (if one isn't specified then the default is assumed.) * - * Open the file using the appropriate mode, and you will be able to read and write to the file. + * Open the file using the appropriate mode, and you will be able to + * read and write to the file. * - * Optionally, you can also call the file's save() method, this will turn the file into an entity in the - * system and permit you to do things like attach tags to the file etc. This is not done automatically since - * there are many occasions where you may want access to file data on datastores using the ElggFile interface - * but do not want to create an Entity reference to it in the system (temporary files for example). + * Optionally, you can also call the file's save() method, this will + * turn the file into an entity in the system and permit you to do + * things like attach tags to the file etc. This is not done automatically + * since there are many occasions where you may want access to file data + * on datastores using the ElggFile interface but do not want to create + * an Entity reference to it in the system (temporary files for example). * - * @author Curverider Ltd + * @class ElggFile + * @package Elgg.Core + * @subpackage DataModel.File */ class ElggFile extends ElggObject { /** Filestore */ @@ -24,12 +27,22 @@ class ElggFile extends ElggObject { /** File handle used to identify this file in a filestore. Created by open. */ private $handle; - protected function initialise_attributes() { - parent::initialise_attributes(); + /** + * Set subtype to 'file'. + * + * @return void + */ + protected function initializeAttributes() { + parent::initializeAttributes(); $this->attributes['subtype'] = "file"; } + /** + * Loads an ElggFile entity. + * + * @param int $guid GUID of the ElggFile object + */ public function __construct($guid = null) { parent::__construct($guid); @@ -41,6 +54,8 @@ class ElggFile extends ElggObject { * Set the filename of this file. * * @param string $name The filename. + * + * @return void */ public function setFilename($name) { $this->filename = $name; @@ -48,33 +63,44 @@ class ElggFile extends ElggObject { /** * Return the filename. + * + * @return string */ public function getFilename() { return $this->filename; } /** - * Return the filename of this file as it is/will be stored on the filestore, which may be different - * to the filename. + * Return the filename of this file as it is/will be stored on the + * filestore, which may be different to the filename. + * + * @return string */ public function getFilenameOnFilestore() { return $this->filestore->getFilenameOnFilestore($this); } - /* + /** * Return the size of the filestore associated with this file * + * @param string $prefix Storage prefix + * @param int $container_guid The container GUID of the checked filestore + * + * @return int */ - public function getFilestoreSize($prefix='',$container_guid=0) { + public function getFilestoreSize($prefix = '', $container_guid = 0) { if (!$container_guid) { $container_guid = $this->container_guid; } $fs = $this->getFilestore(); - return $fs->getSize($prefix,$container_guid); + // @todo add getSize() to ElggFilestore + return $fs->getSize($prefix, $container_guid); } /** * Get the mime type of the file. + * + * @return string */ public function getMimeType() { if ($this->mimetype) { @@ -87,16 +113,63 @@ class ElggFile extends ElggObject { /** * Set the mime type of the file. * - * @param $mimetype The mimetype + * @param string $mimetype The mimetype + * + * @return bool */ public function setMimeType($mimetype) { return $this->mimetype = $mimetype; } /** + * 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. + * + * @return bool */ public function setDescription($description) { $this->description = $description; @@ -106,6 +179,10 @@ class ElggFile extends ElggObject { * Open the file with the given mode * * @param string $mode Either read/write/append + * + * @return resource File handler + * + * @throws IOException|InvalidParameterException */ public function open($mode) { if (!$this->getFilename()) { @@ -117,11 +194,12 @@ class ElggFile extends ElggObject { // Sanity check if ( - ($mode!="read") && - ($mode!="write") && - ($mode!="append") + ($mode != "read") && + ($mode != "write") && + ($mode != "append") ) { - throw new InvalidParameterException(sprintf(elgg_echo('InvalidParameterException:UnrecognisedFileMode'), $mode)); + $msg = elgg_echo('InvalidParameterException:UnrecognisedFileMode', array($mode)); + throw new InvalidParameterException($msg); } // Get the filestore @@ -137,9 +215,11 @@ class ElggFile extends ElggObject { } /** - * Write some data. + * Write data. * * @param string $data The data + * + * @return bool */ public function write($data) { $fs = $this->getFilestore(); @@ -148,10 +228,12 @@ class ElggFile extends ElggObject { } /** - * Read some data. + * Read data. * * @param int $length Amount to read. * @param int $offset The offset to start from. + * + * @return mixed Data or false */ public function read($length, $offset = 0) { $fs = $this->getFilestore(); @@ -171,6 +253,8 @@ class ElggFile extends ElggObject { /** * Close the file and commit changes + * + * @return bool */ public function close() { $fs = $this->getFilestore(); @@ -186,22 +270,32 @@ class ElggFile extends ElggObject { /** * Delete this file. + * + * @return bool */ 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; } /** * Seek a position in the file. * - * @param int $position + * @param int $position Position in bytes + * + * @return bool */ public function seek($position) { $fs = $this->getFilestore(); + // @todo add seek() to ElggFilestore return $fs->seek($this->handle, $position); } @@ -218,6 +312,8 @@ class ElggFile extends ElggObject { /** * Return the size of the file in bytes. + * + * @return int */ public function size() { return $this->filestore->getFileSize($this); @@ -225,6 +321,8 @@ class ElggFile extends ElggObject { /** * Return a boolean value whether the file handle is at the end of the file + * + * @return bool */ public function eof() { $fs = $this->getFilestore(); @@ -232,6 +330,11 @@ class ElggFile extends ElggObject { return $fs->eof($this->handle); } + /** + * Returns if the file exists + * + * @return bool + */ public function exists() { $fs = $this->getFilestore(); @@ -242,6 +345,8 @@ class ElggFile extends ElggObject { * Set a filestore. * * @param ElggFilestore $filestore The file store. + * + * @return void */ public function setFilestore(ElggFilestore $filestore) { $this->filestore = $filestore; @@ -249,8 +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. @@ -258,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(); } @@ -299,6 +411,16 @@ class ElggFile extends ElggObject { return $this->filestore; } + /** + * Save the file + * + * Write the file's data to the filestore and save + * the corresponding entity. + * + * @see ElggObject::save() + * + * @return bool + */ public function save() { if (!parent::save()) { return false; diff --git a/engine/classes/ElggFileCache.php b/engine/classes/ElggFileCache.php index b8989a935..94143f777 100644 --- a/engine/classes/ElggFileCache.php +++ b/engine/classes/ElggFileCache.php @@ -1,164 +1,230 @@ -<?php
-/**
- * ElggFileCache
- * Store cached data in a file store.
- *
- * @author Curverider Ltd <info@elgg.com>
- * @package Elgg
- * @subpackage API
- */
-class ElggFileCache extends ElggCache {
- /**
- * Set the Elgg cache.
- *
- * @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.
- */
- function __construct($cache_path, $max_age = 0, $max_size = 0) {
- $this->set_variable("cache_path", $cache_path);
- $this->set_variable("max_age", $max_age);
- $this->set_variable("max_size", $max_size);
-
- if ($cache_path=="") {
- throw new ConfigurationException(elgg_echo('ConfigurationException:NoCachePath'));
- }
- }
-
- /**
- * Create and return a handle to a file.
- *
- * @param string $filename
- * @param string $rw
- */
- protected function create_file($filename, $rw = "rb") {
- // Create a filename matrix
- $matrix = "";
- $depth = strlen($filename);
- if ($depth > 5) {
- $depth = 5;
- }
-
- // Create full path
- $path = $this->get_variable("cache_path") . $matrix;
- if (!is_dir($path)) {
- mkdir($path, 0700, true);
- }
-
- // Open the file
- if ((!file_exists($path . $filename)) && ($rw=="rb")) {
- return false;
- }
-
- return fopen($path . $filename, $rw);
- }
-
- /**
- * Create a sanitised filename for the file.
- *
- * @param string $filename
- */
- protected function sanitise_filename($filename) {
- // @todo : Writeme
-
- return $filename;
- }
-
- /**
- * Save a key
- *
- * @param string $key
- * @param string $data
- * @return boolean
- */
- public function save($key, $data) {
- $f = $this->create_file($this->sanitise_filename($key), "wb");
- if ($f) {
- $result = fwrite($f, $data);
- fclose($f);
-
- return $result;
- }
-
- return false;
- }
-
- /**
- * Load a key
- *
- * @param string $key
- * @param int $offset
- * @param int $limit
- * @return string
- */
- public function load($key, $offset = 0, $limit = null) {
- $f = $this->create_file($this->sanitise_filename($key));
- if ($f) {
- //fseek($f, $offset);
- if (!$limit) {
- $limit = -1;
- }
- $data = stream_get_contents($f, $limit, $offset);
-
- fclose($f);
-
- return $data;
- }
-
- return false;
- }
-
- /**
- * Invalidate a given key.
- *
- * @param string $key
- * @return bool
- */
- public function delete($key) {
- $dir = $this->get_variable("cache_path");
-
- if (file_exists($dir.$key)) {
- return unlink($dir.$key);
- }
- return TRUE;
- }
-
- public function clear() {
- // @todo writeme
- }
-
- public function __destruct() {
- // @todo Check size and age, clean up accordingly
- $size = 0;
- $dir = $this->get_variable("cache_path");
-
- // Short circuit if both size and age are unlimited
- if (($this->get_variable("max_age")==0) && ($this->get_variable("max_size")==0)) {
- return;
- }
-
- $exclude = array(".","..");
-
- $files = scandir($dir);
- if (!$files) {
- throw new IOException(sprintf(elgg_echo('IOException:NotDirectory'), $dir));
- }
-
- // Perform cleanup
- foreach ($files as $f) {
- if (!in_array($f, $exclude)) {
- $stat = stat($dir.$f);
-
- // Add size
- $size .= $stat['size'];
-
- // Is this older than my maximum date?
- if (($this->get_variable("max_age")>0) && (time() - $stat['mtime'] > $this->get_variable("max_age"))) {
- unlink($dir.$f);
- }
-
- // @todo Size
- }
- }
- }
-}
\ No newline at end of file +<?php +/** + * ElggFileCache + * Store cached data in a file store. + * + * @package Elgg.Core + * @subpackage Caches + */ +class ElggFileCache extends ElggCache { + /** + * Set the Elgg cache. + * + * @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); + $this->setVariable("max_age", $max_age); + $this->setVariable("max_size", $max_size); + + if ($cache_path == "") { + throw new ConfigurationException(elgg_echo('ConfigurationException:NoCachePath')); + } + } + + // @codingStandardsIgnoreStart + /** + * Create and return a handle to a file. + * + * @deprecated 1.8 Use ElggFileCache::createFile() + * + * @param string $filename Filename to save as + * @param string $rw Write mode + * + * @return mixed + */ + protected function create_file($filename, $rw = "rb") { + elgg_deprecated_notice('ElggFileCache::create_file() is deprecated by ::createFile()', 1.8); + + return $this->createFile($filename, $rw); + } + // @codingStandardsIgnoreEnd + + /** + * Create and return a handle to a file. + * + * @param string $filename Filename to save as + * @param string $rw Write mode + * + * @return mixed + */ + protected function createFile($filename, $rw = "rb") { + // Create a filename matrix + $matrix = ""; + $depth = strlen($filename); + if ($depth > 5) { + $depth = 5; + } + + // Create full path + $path = $this->getVariable("cache_path") . $matrix; + if (!is_dir($path)) { + mkdir($path, 0700, true); + } + + // Open the file + if ((!file_exists($path . $filename)) && ($rw == "rb")) { + return false; + } + + return fopen($path . $filename, $rw); + } + + // @codingStandardsIgnoreStart + /** + * Create a sanitised filename for the file. + * + * @deprecated 1.8 Use ElggFileCache::sanitizeFilename() + * + * @param string $filename The filename + * + * @return string + */ + protected function sanitise_filename($filename) { + // @todo : Writeme + + return $filename; + } + // @codingStandardsIgnoreEnd + + /** + * Create a sanitised filename for the file. + * + * @param string $filename The filename + * + * @return string + */ + protected function sanitizeFilename($filename) { + // @todo : Writeme + + return $filename; + } + + /** + * Save a key + * + * @param string $key Name + * @param string $data Value + * + * @return boolean + */ + public function save($key, $data) { + $f = $this->createFile($this->sanitizeFilename($key), "wb"); + if ($f) { + $result = fwrite($f, $data); + fclose($f); + + return $result; + } + + return false; + } + + /** + * Load a key + * + * @param string $key Name + * @param int $offset Offset + * @param int $limit Limit + * + * @return string + */ + public function load($key, $offset = 0, $limit = null) { + $f = $this->createFile($this->sanitizeFilename($key)); + if ($f) { + if (!$limit) { + $limit = -1; + } + + $data = stream_get_contents($f, $limit, $offset); + + fclose($f); + + return $data; + } + + return false; + } + + /** + * Invalidate a given key. + * + * @param string $key Name + * + * @return bool + */ + public function delete($key) { + $dir = $this->getVariable("cache_path"); + + if (file_exists($dir . $key)) { + return unlink($dir . $key); + } + return TRUE; + } + + /** + * Delete all files in the directory of this file cache + * + * @return void + */ + public function clear() { + $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); + } + } + } + + /** + * Preform cleanup and invalidates cache upon object destruction + * + * @throws IOException + */ + public function __destruct() { + // @todo Check size and age, clean up accordingly + $size = 0; + $dir = $this->getVariable("cache_path"); + + // Short circuit if both size and age are unlimited + if (($this->getVariable("max_age") == 0) && ($this->getVariable("max_size") == 0)) { + return; + } + + $exclude = array(".", ".."); + + $files = scandir($dir); + if (!$files) { + throw new IOException(elgg_echo('IOException:NotDirectory', array($dir))); + } + + // Perform cleanup + foreach ($files as $f) { + if (!in_array($f, $exclude)) { + $stat = stat($dir . $f); + + // Add size + $size .= $stat['size']; + + // Is this older than my maximum date? + if (($this->getVariable("max_age") > 0) && (time() - $stat['mtime'] > $this->getVariable("max_age"))) { + unlink($dir . $f); + } + + // @todo Size + } + } + } +} diff --git a/engine/classes/ElggFilestore.php b/engine/classes/ElggFilestore.php index 3311842a9..16430feac 100644 --- a/engine/classes/ElggFilestore.php +++ b/engine/classes/ElggFilestore.php @@ -1,115 +1,139 @@ -<?php
-/**
- * @class ElggFilestore
- * This class defines the interface for all elgg data repositories.
- * @author Curverider Ltd
- */
-abstract class ElggFilestore {
- /**
- * Attempt to open the file $file for storage or writing.
- *
- * @param ElggFile $file
- * @param string $mode "read", "write", "append"
- * @return mixed A handle to the opened file or false on error.
- */
- abstract public function open(ElggFile $file, $mode);
-
- /**
- * Write data to a given file handle.
- *
- * @param mixed $f The file handle - exactly what this is depends on the file system
- * @param string $data The binary string of data to write
- * @return int Number of bytes written.
- */
- abstract public function write($f, $data);
-
- /**
- * Read data from a filestore.
- *
- * @param mixed $f The file handle
- * @param int $length Length in bytes to read.
- * @param int $offset The optional offset.
- * @return mixed String of data or false on error.
- */
- abstract public function read($f, $length, $offset = 0);
-
- /**
- * Seek a given position within a file handle.
- *
- * @param mixed $f The file handle.
- * @param int $position The position.
- */
- abstract public function seek($f, $position);
-
- /**
- * Return a whether the end of a file has been reached.
- *
- * @param mixed $f The file handle.
- * @return boolean
- */
- abstract public function eof($f);
-
- /**
- * Return the current position in an open file.
- *
- * @param mixed $f The file handle.
- * @return int
- */
- abstract public function tell($f);
-
- /**
- * Close a given file handle.
- *
- * @param mixed $f
- */
- abstract public function close($f);
-
- /**
- * Delete the file associated with a given file handle.
- *
- * @param ElggFile $file
- */
- abstract public function delete(ElggFile $file);
-
- /**
- * Return the size in bytes for a given file.
- *
- * @param ElggFile $file
- */
- abstract public function getFileSize(ElggFile $file);
-
- /**
- * Return the filename of a given file as stored on the filestore.
- *
- * @param ElggFile $file
- */
- abstract public function getFilenameOnFilestore(ElggFile $file);
-
- /**
- * Get the filestore's creation parameters as an associative array.
- * Used for serialisation and for storing the creation details along side a file object.
- *
- * @return array
- */
- abstract public function getParameters();
-
- /**
- * Set the parameters from the associative array produced by $this->getParameters().
- */
- abstract public function setParameters(array $parameters);
-
- /**
- * Get the contents of the whole file.
- *
- * @param mixed $file The file handle.
- * @return mixed The file contents.
- */
- abstract public function grabFile(ElggFile $file);
-
- /**
- * Return whether a file physically exists or not.
- *
- * @param ElggFile $file
- */
- abstract public function exists(ElggFile $file);
-}
\ No newline at end of file +<?php +/** + * This class defines the interface for all elgg data repositories. + * + * @package Elgg.Core + * @subpackage DataStorage + * @class ElggFilestore + */ +abstract class ElggFilestore { + /** + * Attempt to open the file $file for storage or writing. + * + * @param ElggFile $file A file + * @param string $mode "read", "write", "append" + * + * @return mixed A handle to the opened file or false on error. + */ + abstract public function open(ElggFile $file, $mode); + + /** + * Write data to a given file handle. + * + * @param mixed $f The file handle - exactly what this is depends on the file system + * @param string $data The binary string of data to write + * + * @return int Number of bytes written. + */ + abstract public function write($f, $data); + + /** + * Read data from a filestore. + * + * @param mixed $f The file handle + * @param int $length Length in bytes to read. + * @param int $offset The optional offset. + * + * @return mixed String of data or false on error. + */ + abstract public function read($f, $length, $offset = 0); + + /** + * Seek a given position within a file handle. + * + * @param mixed $f The file handle. + * @param int $position The position. + * + * @return void + */ + abstract public function seek($f, $position); + + /** + * Return a whether the end of a file has been reached. + * + * @param mixed $f The file handle. + * + * @return boolean + */ + abstract public function eof($f); + + /** + * Return the current position in an open file. + * + * @param mixed $f The file handle. + * + * @return int + */ + abstract public function tell($f); + + /** + * Close a given file handle. + * + * @param mixed $f The file handle + * + * @return bool + */ + abstract public function close($f); + + /** + * Delete the file associated with a given file handle. + * + * @param ElggFile $file The file + * + * @return bool + */ + abstract public function delete(ElggFile $file); + + /** + * Return the size in bytes for a given file. + * + * @param ElggFile $file The file + * + * @return int + */ + abstract public function getFileSize(ElggFile $file); + + /** + * Return the filename of a given file as stored on the filestore. + * + * @param ElggFile $file The file + * + * @return string + */ + abstract public function getFilenameOnFilestore(ElggFile $file); + + /** + * Get the filestore's creation parameters as an associative array. + * Used for serialisation and for storing the creation details along side a file object. + * + * @return array + */ + abstract public function getParameters(); + + /** + * Set the parameters from the associative array produced by $this->getParameters(). + * + * @param array $parameters A list of parameters + * + * @return bool + */ + abstract public function setParameters(array $parameters); + + /** + * Get the contents of the whole file. + * + * @param mixed $file The file handle. + * + * @return mixed The file contents. + */ + abstract public function grabFile(ElggFile $file); + + /** + * Return whether a file physically exists or not. + * + * @param ElggFile $file The file + * + * @return bool + */ + abstract public function exists(ElggFile $file); +} diff --git a/engine/classes/ElggGroup.php b/engine/classes/ElggGroup.php index 9713ca39e..7e69b7a84 100644 --- a/engine/classes/ElggGroup.php +++ b/engine/classes/ElggGroup.php @@ -1,299 +1,393 @@ -<?php
-
-/**
- * @class ElggGroup Class representing a container for other elgg entities.
- * @author Curverider Ltd
- */
-class ElggGroup extends ElggEntity
- implements Friendable {
-
- protected function initialise_attributes() {
- parent::initialise_attributes();
-
- $this->attributes['type'] = "group";
- $this->attributes['name'] = "";
- $this->attributes['description'] = "";
- $this->attributes['tables_split'] = 2;
- }
-
- /**
- * 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.
- * @throws Exception if there was a problem creating the user.
- */
- function __construct($guid = null) {
- $this->initialise_attributes();
-
- if (!empty($guid)) {
- // Is $guid is a DB row - either a entity row, or a user table row.
- if ($guid instanceof stdClass) {
- // Load the rest
- if (!$this->load($guid->guid)) {
- throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid));
- }
- }
- // Is $guid is an ElggGroup? Use a copy constructor
- else if ($guid instanceof ElggGroup) {
- 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) {
- throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggGroup'));
- }
- // We assume if we have got this far, $guid is an int
- else if (is_numeric($guid)) {
- if (!$this->load($guid)) {
- throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid));
- }
- }
-
- else {
- throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue'));
- }
- }
- }
-
- /**
- * Add an ElggObject to this group.
- *
- * @param ElggObject $object The object.
- * @return bool
- */
- public function addObjectToGroup(ElggObject $object) {
- return add_object_to_group($this->getGUID(), $object->getGUID());
- }
-
- /**
- * Remove an object from the containing group.
- *
- * @param int $guid The guid of the object.
- * @return bool
- */
- public function removeObjectFromGroup($guid) {
- return remove_object_from_group($this->getGUID(), $guid);
- }
-
- public function get($name) {
- if ($name == 'username') {
- return 'group:' . $this->getGUID();
- }
- return parent::get($name);
- }
-
-/**
- * Start friendable compatibility block:
- *
- * public function addFriend($friend_guid);
- public function removeFriend($friend_guid);
- public function isFriend();
- public function isFriendsWith($user_guid);
- public function isFriendOf($user_guid);
- public function getFriends($subtype = "", $limit = 10, $offset = 0);
- public function getFriendsOf($subtype = "", $limit = 10, $offset = 0);
- public function getObjects($subtype="", $limit = 10, $offset = 0);
- public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0);
- public function countObjects($subtype = "");
- */
-
- /**
- * For compatibility with Friendable
- */
- public function addFriend($friend_guid) {
- return $this->join(get_entity($friend_guid));
- }
-
- /**
- * For compatibility with Friendable
- */
- public function removeFriend($friend_guid) {
- return $this->leave(get_entity($friend_guid));
- }
-
- /**
- * For compatibility with Friendable
- */
- public function isFriend() {
- return $this->isMember();
- }
-
- /**
- * For compatibility with Friendable
- */
- public function isFriendsWith($user_guid) {
- return $this->isMember($user_guid);
- }
-
- /**
- * For compatibility with Friendable
- */
- public function isFriendOf($user_guid) {
- return $this->isMember($user_guid);
- }
-
- /**
- * For compatibility with Friendable
- */
- public function getFriends($subtype = "", $limit = 10, $offset = 0) {
- return get_group_members($this->getGUID(), $limit, $offset);
- }
-
- /**
- * For compatibility with Friendable
- */
- public function getFriendsOf($subtype = "", $limit = 10, $offset = 0) {
- return get_group_members($this->getGUID(), $limit, $offset);
- }
-
- /**
- * Get objects contained in this group.
- *
- * @param string $subtype
- * @param int $limit
- * @param int $offset
- * @return mixed
- */
- public function getObjects($subtype="", $limit = 10, $offset = 0) {
- return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", $limit, $offset, false);
- }
-
- /**
- * For compatibility with Friendable
- */
- public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0) {
- return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", $limit, $offset, false);
- }
-
- /**
- * For compatibility with Friendable
- */
- public function countObjects($subtype = "") {
- return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", 10, 0, true);
- }
-
-/**
- * End friendable compatibility block
- */
-
- /**
- * Get a list of group members.
- *
- * @param int $limit
- * @param int $offset
- * @return mixed
- */
- public function getMembers($limit = 10, $offset = 0, $count = false) {
- return get_group_members($this->getGUID(), $limit, $offset, 0 , $count);
- }
-
- /**
- * Returns whether the current group is public membership or not.
- * @return bool
- */
- public function isPublicMembership() {
- if ($this->membership == ACCESS_PUBLIC) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Return whether a given user is a member of this group or not.
- *
- * @param ElggUser $user The user
- * @return bool
- */
- public function isMember($user = 0) {
- if (!($user instanceof ElggUser)) {
- $user = get_loggedin_user();
- }
- if (!($user instanceof ElggUser)) {
- return false;
- }
- return is_group_member($this->getGUID(), $user->getGUID());
- }
-
- /**
- * Join an elgg user to this group.
- *
- * @param ElggUser $user
- * @return bool
- */
- public function join(ElggUser $user) {
- return join_group($this->getGUID(), $user->getGUID());
- }
-
- /**
- * Remove a user from the group.
- *
- * @param ElggUser $user
- */
- 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.
- *
- * @param int $guid
- */
- 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']!='group') {
- throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class()));
- }
-
- // 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;
- }
-
- return true;
- }
-
- /**
- * Override the save function.
- */
- public function save() {
- // Save generic stuff
- if (!parent::save()) {
- return false;
- }
-
- // Now save specific stuff
- return create_group_entity($this->get('guid'), $this->get('name'), $this->get('description'));
- }
-
- // EXPORTABLE INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Return an array of fields which can be exported.
- */
- public function getExportableValues() {
- return array_merge(parent::getExportableValues(), array(
- 'name',
- 'description',
- ));
- }
-}
\ No newline at end of file +<?php + +/** + * Class representing a container for other elgg entities. + * + * @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 + */ + protected function initializeAttributes() { + parent::initializeAttributes(); + + $this->attributes['type'] = "group"; + $this->attributes['name'] = NULL; + $this->attributes['description'] = NULL; + $this->attributes['tables_split'] = 2; + } + + /** + * Construct a new group entity, optionally from a given guid value. + * + * @param mixed $guid If an int, load that GUID. + * If an entity table db row, then will load the rest of the data. + * + * @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 entity table DB row + if ($guid instanceof stdClass) { + // Load the rest + if (!$this->load($guid)) { + $msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid)); + throw new IOException($msg); + } + } 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; + } + } else if ($guid instanceof ElggEntity) { + // @todo why separate from else + throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggGroup')); + } else if (is_numeric($guid)) { + // $guid is a GUID so load entity + if (!$this->load($guid)) { + throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid))); + } + } else { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); + } + } + } + + /** + * Add an ElggObject to this group. + * + * @param ElggObject $object The object. + * + * @return bool + */ + public function addObjectToGroup(ElggObject $object) { + return add_object_to_group($this->getGUID(), $object->getGUID()); + } + + /** + * Remove an object from the containing group. + * + * @param int $guid The guid of the object. + * + * @return bool + */ + public function removeObjectFromGroup($guid) { + return remove_object_from_group($this->getGUID(), $guid); + } + + /** + * Returns an attribute or metadata. + * + * @see ElggEntity::get() + * + * @param string $name Name + * + * @return mixed + */ + public function get($name) { + if ($name == 'username') { + return 'group:' . $this->getGUID(); + } + return parent::get($name); + } + + /** + * Start friendable compatibility block: + * + * public function addFriend($friend_guid); + public function removeFriend($friend_guid); + public function isFriend(); + public function isFriendsWith($user_guid); + public function isFriendOf($user_guid); + public function getFriends($subtype = "", $limit = 10, $offset = 0); + public function getFriendsOf($subtype = "", $limit = 10, $offset = 0); + public function getObjects($subtype="", $limit = 10, $offset = 0); + public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0); + public function countObjects($subtype = ""); + */ + + /** + * For compatibility with Friendable. + * + * Join a group when you friend ElggGroup. + * + * @param int $friend_guid The GUID of the user joining the group. + * + * @return bool + */ + public function addFriend($friend_guid) { + return $this->join(get_entity($friend_guid)); + } + + /** + * For compatibility with Friendable + * + * Leave group when you unfriend ElggGroup. + * + * @param int $friend_guid The GUID of the user leaving. + * + * @return bool + */ + public function removeFriend($friend_guid) { + return $this->leave(get_entity($friend_guid)); + } + + /** + * For compatibility with Friendable + * + * Friending a group adds you as a member + * + * @return bool + */ + public function isFriend() { + return $this->isMember(); + } + + /** + * For compatibility with Friendable + * + * @param int $user_guid The GUID of a user to check. + * + * @return bool + */ + public function isFriendsWith($user_guid) { + return $this->isMember($user_guid); + } + + /** + * For compatibility with Friendable + * + * @param int $user_guid The GUID of a user to check. + * + * @return bool + */ + public function isFriendOf($user_guid) { + return $this->isMember($user_guid); + } + + /** + * For compatibility with Friendable + * + * @param string $subtype The GUID of a user to check. + * @param int $limit Limit + * @param int $offset Offset + * + * @return bool + */ + public function getFriends($subtype = "", $limit = 10, $offset = 0) { + return get_group_members($this->getGUID(), $limit, $offset); + } + + /** + * For compatibility with Friendable + * + * @param string $subtype The GUID of a user to check. + * @param int $limit Limit + * @param int $offset Offset + * + * @return bool + */ + public function getFriendsOf($subtype = "", $limit = 10, $offset = 0) { + return get_group_members($this->getGUID(), $limit, $offset); + } + + /** + * Get objects contained in this group. + * + * @param string $subtype Entity subtype + * @param int $limit Limit + * @param int $offset Offset + * + * @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); + } + + /** + * For compatibility with Friendable + * + * @param string $subtype Entity subtype + * @param int $limit Limit + * @param int $offset Offset + * + * @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); + } + + /** + * For compatibility with Friendable + * + * @param string $subtype Subtype of entities + * + * @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); + } + + /** + * End friendable compatibility block + */ + + /** + * Get a list of group members. + * + * @param int $limit Limit + * @param int $offset Offset + * @param bool $count Count + * + * @return mixed + */ + public function getMembers($limit = 10, $offset = 0, $count = false) { + return get_group_members($this->getGUID(), $limit, $offset, 0, $count); + } + + /** + * Returns whether the current group is public membership or not. + * + * @return bool + */ + public function isPublicMembership() { + if ($this->membership == ACCESS_PUBLIC) { + return true; + } + + return false; + } + + /** + * Return whether a given user is a member of this group or not. + * + * @param ElggUser $user The user + * + * @return bool + */ + public function isMember($user = null) { + if (!($user instanceof ElggUser)) { + $user = elgg_get_logged_in_user_entity(); + } + if (!($user instanceof ElggUser)) { + return false; + } + return is_group_member($this->getGUID(), $user->getGUID()); + } + + /** + * Join an elgg user to this group. + * + * @param ElggUser $user User + * + * @return bool + */ + public function join(ElggUser $user) { + return join_group($this->getGUID(), $user->getGUID()); + } + + /** + * Remove a user from the group. + * + * @param ElggUser $user User + * + * @return bool + */ + public function leave(ElggUser $user) { + return leave_group($this->getGUID(), $user->getGUID()); + } + + /** + * Load the ElggGroup data from the database + * + * @param mixed $guid GUID of an ElggGroup entity or database row from entity table + * + * @return bool + */ + protected function 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; + } + + $this->attributes = $attrs; + $this->attributes['tables_loaded'] = 2; + _elgg_cache_entity($this); + + return true; + } + + /** + * Override the save function. + * + * @return bool + */ + public function save() { + // Save generic stuff + if (!parent::save()) { + return false; + } + + // Now save specific stuff + + _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 //////////////////////////////////////////////////////////// + + /** + * Return an array of fields which can be exported. + * + * @return array + */ + public function getExportableValues() { + return array_merge(parent::getExportableValues(), array( + 'name', + 'description', + )); + } + + /** + * 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 8c50d7dfb..c2f468815 100644 --- a/engine/classes/ElggHMACCache.php +++ b/engine/classes/ElggHMACCache.php @@ -1,94 +1,99 @@ -<?php
-/**
- * ElggHMACCache
- * Store cached data in a temporary database, only used by the HMAC stuff.
- *
- * @author Curverider Ltd <info@elgg.com>
- * @package Elgg
- * @subpackage API
- */
-class ElggHMACCache extends ElggCache {
- /**
- * Set the Elgg cache.
- *
- * @param int $max_age Maximum age in seconds, 0 if no limit.
- */
- function __construct($max_age = 0) {
- $this->set_variable("max_age", $max_age);
- }
-
- /**
- * Save a key
- *
- * @param string $key
- * @param string $data
- * @return boolean
- */
- public function save($key, $data) {
- global $CONFIG;
-
- $key = sanitise_string($key);
- $time = time();
-
- return insert_data("INSERT into {$CONFIG->dbprefix}hmac_cache (hmac, ts) VALUES ('$key', '$time')");
- }
-
- /**
- * Load a key
- *
- * @param string $key
- * @param int $offset
- * @param int $limit
- * @return string
- */
- public function load($key, $offset = 0, $limit = null) {
- global $CONFIG;
-
- $key = sanitise_string($key);
-
- $row = get_data_row("SELECT * from {$CONFIG->dbprefix}hmac_cache where hmac='$key'");
- if ($row) {
- return $row->hmac;
- }
-
- return false;
- }
-
- /**
- * Invalidate a given key.
- *
- * @param string $key
- * @return bool
- */
- public function delete($key) {
- global $CONFIG;
-
- $key = sanitise_string($key);
-
- return delete_data("DELETE from {$CONFIG->dbprefix}hmac_cache where hmac='$key'");
- }
-
- /**
- * Clear out all the contents of the cache.
- *
- * Not currently implemented in this cache type.
- */
- public function clear() {
- return true;
- }
-
- /**
- * Clean out old stuff.
- *
- */
- public function __destruct() {
- global $CONFIG;
-
- $time = time();
- $age = (int)$this->get_variable("max_age");
-
- $expires = $time-$age;
-
- delete_data("DELETE from {$CONFIG->dbprefix}hmac_cache where ts<$expires");
- }
-}
\ No newline at end of file +<?php +/** + * ElggHMACCache + * Store cached data in a temporary database, only used by the HMAC stuff. + * + * @package Elgg.Core + * @subpackage HMAC + */ +class ElggHMACCache extends ElggCache { + /** + * Set the Elgg cache. + * + * @param int $max_age Maximum age in seconds, 0 if no limit. + */ + function __construct($max_age = 0) { + $this->setVariable("max_age", $max_age); + } + + /** + * Save a key + * + * @param string $key Name + * @param string $data Value + * + * @return boolean + */ + public function save($key, $data) { + global $CONFIG; + + $key = sanitise_string($key); + $time = time(); + + $query = "INSERT into {$CONFIG->dbprefix}hmac_cache (hmac, ts) VALUES ('$key', '$time')"; + return insert_data($query); + } + + /** + * Load a key + * + * @param string $key Name + * @param int $offset Offset + * @param int $limit Limit + * + * @return string + */ + public function load($key, $offset = 0, $limit = null) { + global $CONFIG; + + $key = sanitise_string($key); + + $row = get_data_row("SELECT * from {$CONFIG->dbprefix}hmac_cache where hmac='$key'"); + if ($row) { + return $row->hmac; + } + + return false; + } + + /** + * Invalidate a given key. + * + * @param string $key Name + * + * @return bool + */ + public function delete($key) { + global $CONFIG; + + $key = sanitise_string($key); + + return delete_data("DELETE from {$CONFIG->dbprefix}hmac_cache where hmac='$key'"); + } + + /** + * Clear out all the contents of the cache. + * + * Not currently implemented in this cache type. + * + * @return true + */ + public function clear() { + return true; + } + + /** + * Clean out old stuff. + * + */ + public function __destruct() { + global $CONFIG; + + $time = time(); + $age = (int)$this->getVariable("max_age"); + + $expires = $time - $age; + + delete_data("DELETE from {$CONFIG->dbprefix}hmac_cache where ts<$expires"); + } +} 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 5e898c26a..91d50ab89 100644 --- a/engine/classes/ElggMemcache.php +++ b/engine/classes/ElggMemcache.php @@ -1,151 +1,203 @@ -<?php
-/**
- * Memcache wrapper class.
- * @author Curverider Ltd <info@elgg.com>
- */
-class ElggMemcache extends ElggSharedMemoryCache {
- /**
- * Minimum version of memcached needed to run
- *
- */
- private static $MINSERVERVERSION = '1.1.12';
-
- /**
- * Memcache object
- */
- private $memcache;
-
- /**
- * Expiry of saved items (default timeout after a day to prevent anything getting too stale)
- */
- private $expires = 86400;
-
- /**
- * The version of memcache running
- */
- private $version = 0;
-
- /**
- * Connect to memcache.
- *
- * @param string $cache_id The namespace for this cache to write to - note, namespaces of the same name are shared!
- */
- function __construct($namespace = 'default') {
- global $CONFIG;
-
- $this->setNamespace($namespace);
-
- // Do we have memcache?
- if (!class_exists('Memcache')) {
- throw new ConfigurationException(elgg_echo('memcache:notinstalled'));
- }
-
- // Create memcache object
- $this->memcache = new Memcache;
-
- // Now add servers
- if (!$CONFIG->memcache_servers) {
- throw new ConfigurationException(elgg_echo('memcache:noservers'));
- }
-
- if (is_callable($this->memcache, 'addServer')) {
- foreach ($CONFIG->memcache_servers as $server) {
- if (is_array($server)) {
- $this->memcache->addServer(
- $server[0],
- isset($server[1]) ? $server[1] : 11211,
- isset($server[2]) ? $server[2] : true,
- isset($server[3]) ? $server[3] : null,
- isset($server[4]) ? $server[4] : 1,
- isset($server[5]) ? $server[5] : 15,
- isset($server[6]) ? $server[6] : true
- );
-
- } else {
- $this->memcache->addServer($server, 11211);
- }
- }
- } else {
- elgg_log(elgg_echo('memcache:noaddserver'), 'ERROR');
-
- $server = $CONFIG->memcache_servers[0];
- if (is_array($server)) {
- $this->memcache->connect($server[0], $server[1]);
- } else {
- $this->memcache->addServer($server, 11211);
- }
- }
-
- // Get version
- $this->version = $this->memcache->getversion();
- if (version_compare($this->version, ElggMemcache::$MINSERVERVERSION, '<')) {
- throw new ConfigurationException(sprintf(elgg_echo('memcache:versiontoolow'), ElggMemcache::$MINSERVERVERSION, $this->version));
- }
-
- // Set some defaults
- if (isset($CONFIG->memcache_expires)) {
- $this->expires = $CONFIG->memcache_expires;
- }
- }
-
- /**
- * Set the default expiry.
- *
- * @param int $expires The lifetime as a unix timestamp or time from now. Defaults forever.
- */
- public function setDefaultExpiry($expires = 0) {
- $this->expires = $expires;
- }
-
- /**
- * 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 make_memcache_key($key) {
- $prefix = $this->getNamespace() . ":";
-
- if (strlen($prefix.$key)> 250) {
- $key = md5($key);
- }
-
- return $prefix.$key;
- }
-
- public function save($key, $data) {
- $key = $this->make_memcache_key($key);
-
- $result = $this->memcache->set($key, $data, null, $this->expires);
- if (!$result) {
- elgg_log("MEMCACHE: FAILED TO SAVE $key", 'ERROR');
- }
-
- return $result;
- }
-
- public function load($key, $offset = 0, $limit = null) {
- $key = $this->make_memcache_key($key);
-
- $result = $this->memcache->get($key);
- if (!$result) {
- elgg_log("MEMCACHE: FAILED TO LOAD $key", 'ERROR');
- }
-
- return $result;
- }
-
- public function delete($key) {
- $key = $this->make_memcache_key($key);
-
- return $this->memcache->delete($key, 0);
- }
-
- public function clear() {
- // DISABLE clearing for now - you must use delete on a specific key.
- return true;
-
- // @todo Namespaces as in #532
- }
-}
+<?php +/** + * Memcache wrapper class. + * + * @package Elgg.Core + * @subpackage Memcache + */ +class ElggMemcache extends ElggSharedMemoryCache { + /** + * Minimum version of memcached needed to run + * + */ + private static $MINSERVERVERSION = '1.1.12'; + + /** + * Memcache object + */ + private $memcache; + + /** + * Expiry of saved items (default timeout after a day to prevent anything getting too stale) + */ + private $expires = 86400; + + /** + * The version of memcache running + */ + private $version = 0; + + /** + * Connect to memcache. + * + * @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; + + $this->setNamespace($namespace); + + // Do we have memcache? + if (!class_exists('Memcache')) { + throw new ConfigurationException('PHP memcache module not installed, you must install php5-memcache'); + } + + // Create memcache object + $this->memcache = new Memcache; + + // Now add servers + if (!$CONFIG->memcache_servers) { + throw new ConfigurationException('No memcache servers defined, please populate the $CONFIG->memcache_servers variable'); + } + + if (is_callable(array($this->memcache, 'addServer'))) { + foreach ($CONFIG->memcache_servers as $server) { + if (is_array($server)) { + $this->memcache->addServer( + $server[0], + isset($server[1]) ? $server[1] : 11211, + isset($server[2]) ? $server[2] : FALSE, + isset($server[3]) ? $server[3] : 1, + isset($server[4]) ? $server[4] : 1, + isset($server[5]) ? $server[5] : 15, + isset($server[6]) ? $server[6] : TRUE + ); + + } else { + $this->memcache->addServer($server, 11211); + } + } + } else { + // don't use elgg_echo() here because most of the config hasn't been loaded yet + // and it caches the language, which is hard coded in $CONFIG->language as en. + // overriding it with real values later has no effect because it's already cached. + elgg_log("This version of the PHP memcache API doesn't support multiple servers.", 'ERROR'); + + $server = $CONFIG->memcache_servers[0]; + if (is_array($server)) { + $this->memcache->connect($server[0], $server[1]); + } else { + $this->memcache->addServer($server, 11211); + } + } + + // Get version + $this->version = $this->memcache->getVersion(); + if (version_compare($this->version, 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); + } + + // Set some defaults + if (isset($CONFIG->memcache_expires)) { + $this->expires = $CONFIG->memcache_expires; + } + } + + /** + * Set the default expiry. + * + * @param int $expires The lifetime as a unix timestamp or time from now. Defaults forever. + * + * @return void + */ + public function setDefaultExpiry($expires = 0) { + $this->expires = $expires; + } + + /** + * 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) { + $prefix = $this->getNamespace() . ":"; + + if (strlen($prefix . $key) > 250) { + $key = md5($key); + } + + return $prefix . $key; + } + + /** + * Saves a name and value to the cache + * + * @param string $key Name + * @param string $data Value + * @param integer $expires Expires (in seconds) + * + * @return bool + */ + public function save($key, $data, $expires = null) { + $key = $this->makeMemcacheKey($key); + + if ($expires === null) { + $expires = $this->expires; + } + + $result = $this->memcache->set($key, $data, null, $expires); + if ($result === false) { + elgg_log("MEMCACHE: FAILED TO SAVE $key", 'ERROR'); + } + + return $result; + } + + /** + * Retrieves data. + * + * @param string $key Name of data to retrieve + * @param int $offset Offset + * @param int $limit Limit + * + * @return mixed + */ + public function load($key, $offset = 0, $limit = null) { + $key = $this->makeMemcacheKey($key); + + $result = $this->memcache->get($key); + if ($result === false) { + elgg_log("MEMCACHE: FAILED TO LOAD $key", 'ERROR'); + } + + return $result; + } + + /** + * Delete data + * + * @param string $key Name of data + * + * @return bool + */ + public function delete($key) { + $key = $this->makeMemcacheKey($key); + + return $this->memcache->delete($key, 0); + } + + /** + * Clears the entire cache? + * + * @todo write or remove. + * + * @return true + */ + public function clear() { + // DISABLE clearing for now - you must use delete on a specific key. + return true; + + // @todo Namespaces as in #532 + } +} 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 631b73c8f..3a8e2d817 100644 --- a/engine/classes/ElggMetadata.php +++ b/engine/classes/ElggMetadata.php @@ -1,114 +1,158 @@ -<?php
-
-/**
- * ElggMetadata
- * This class describes metadata that can be attached to ElggEntities.
- *
- * @author Curverider Ltd <info@elgg.com>
- * @package Elgg
- * @subpackage Core
- */
-class ElggMetadata extends ElggExtender {
- /**
- * Construct a new site object, optionally from a given id value or row.
- *
- * @param mixed $id
- */
- function __construct($id = null) {
- $this->attributes = array();
-
- if (!empty($id)) {
- // 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;
- }
- $this->attributes['type'] = "metadata";
- }
- }
- }
-
- /**
- * Class member get overloading
- *
- * @param string $name
- * @return mixed
- */
- function __get($name) {
- return $this->get($name);
- }
-
- /**
- * Class member set overloading
- *
- * @param string $name
- * @param mixed $value
- * @return mixed
- */
- function __set($name, $value) {
- return $this->set($name, $value);
- }
-
- /**
- * Determines whether or not the user can edit this piece of metadata
- *
- * @return true|false Depending on permissions
- */
- function canEdit() {
- if ($entity = get_entity($this->get('entity_guid'))) {
- return $entity->canEditMetadata($this);
- }
- return false;
- }
-
- /**
- * Save matadata object
- *
- * @return int the metadata object id
- */
- function save() {
- if ($this->id > 0) {
- return update_metadata($this->id, $this->name, $this->value, $this->value_type, $this->owner_guid, $this->access_id);
- } else {
- $this->id = create_metadata($this->entity_guid, $this->name, $this->value, $this->value_type, $this->owner_guid, $this->access_id);
- if (!$this->id) {
- throw new IOException(sprintf(elgg_echo('IOException:UnableToSaveNew'), get_class()));
- }
- return $this->id;
- }
- }
-
- /**
- * Delete a given metadata.
- */
- function delete() {
- return delete_metadata($this->id);
- }
-
- /**
- * Get a url for this item of metadata.
- *
- * @return string
- */
- public function getURL() {
- return get_metadata_url($this->id);
- }
-
- // SYSTEM LOG INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * For a given ID, return the object associated with it.
- * This is used by the river functionality primarily.
- * This is useful for checking access permissions etc on objects.
- */
- public function getObjectFromID($id) {
- return get_metadata($id);
- }
-}
\ No newline at end of file +<?php + +/** + * ElggMetadata + * This class describes metadata that can be attached to ElggEntities. + * + * @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 metadata object + * + * @param mixed $id ID of metadata or a database row as stdClass object + */ + function __construct($id = null) { + $this->initializeAttributes(); + + if (!empty($id)) { + // Create from db row + if ($id instanceof stdClass) { + $metadata = $id; + + $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; + } + } + } + + /** + * Determines whether or not the user can edit this piece of metadata + * + * @param int $user_guid The GUID of the user (defaults to currently logged in user) + * + * @return bool Depending on permissions + */ + function canEdit($user_guid = 0) { + if ($entity = get_entity($this->get('entity_guid'))) { + return $entity->canEditMetadata($this, $user_guid); + } + return false; + } + + /** + * Save metadata object + * + * @return int|bool the metadata object id or true if updated + * + * @throws IOException + */ + function save() { + if ($this->id > 0) { + return update_metadata($this->id, $this->name, $this->value, + $this->value_type, $this->owner_guid, $this->access_id); + } else { + $this->id = create_metadata($this->entity_guid, $this->name, $this->value, + $this->value_type, $this->owner_guid, $this->access_id); + + if (!$this->id) { + throw new IOException(elgg_echo('IOException:UnableToSaveNew', array(get_class()))); + } + return $this->id; + } + } + + /** + * Delete the metadata + * + * @return bool + */ + function delete() { + $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; + } + + /** + * Get a url for this item of metadata. + * + * @return string + */ + public function getURL() { + return get_metadata_url($this->id); + } + + // SYSTEM LOG INTERFACE //////////////////////////////////////////////////////////// + + /** + * For a given ID, return the object associated with it. + * This is used by the river functionality primarily. + * This is useful for checking access permissions etc on objects. + * + * @param int $id Metadata ID + * + * @return ElggMetadata + */ + public function getObjectFromID($id) { + return elgg_get_metadata_from_id($id); + } +} diff --git a/engine/classes/ElggObject.php b/engine/classes/ElggObject.php index af67ef3f6..aeaa3ba5c 100644 --- a/engine/classes/ElggObject.php +++ b/engine/classes/ElggObject.php @@ -1,199 +1,216 @@ -<?php
-
-/**
- * ElggObject
- * Representation of an "object" in the system.
- *
- * @package Elgg
- * @subpackage Core
- */
-class ElggObject extends ElggEntity {
- /**
- * Initialise the attributes array.
- * This is vital to distinguish between metadata and base parameters.
- *
- * Place your base parameters here.
- */
- protected function initialise_attributes() {
- parent::initialise_attributes();
-
- $this->attributes['type'] = "object";
- $this->attributes['title'] = "";
- $this->attributes['description'] = "";
- $this->attributes['tables_split'] = 2;
- }
-
- /**
- * Construct a new object 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.
- * @throws Exception if there was a problem creating the object.
- */
- function __construct($guid = null) {
- $this->initialise_attributes();
-
- if (!empty($guid)) {
- // Is $guid is a DB row - either a entity row, or a object table row.
- if ($guid instanceof stdClass) {
- // Load the rest
- if (!$this->load($guid->guid)) {
- throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid));
- }
- }
-
- // Is $guid is an ElggObject? Use a copy constructor
- else if ($guid instanceof ElggObject) {
- 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) {
- throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggObject'));
- }
-
- // We assume if we have got this far, $guid is an int
- else if (is_numeric($guid)) {
- if (!$this->load($guid)) {
- throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid));
- }
- }
-
- else {
- throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue'));
- }
- }
- }
-
- /**
- * Override the load function.
- * This function will ensure that all data is loaded (were possible), so
- * if only part of the ElggObject is loaded, it'll load the rest.
- *
- * @param int $guid
- * @return true|false
- */
- 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') {
- throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class()));
- }
-
- // 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'] ++;
- }
-
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach($objarray as $key => $value) {
- $this->attributes[$key] = $value;
- }
-
- return true;
- }
-
- /**
- * Override the save function.
- * @return true|false
- */
- public function save() {
- // Save generic stuff
- if (!parent::save()) {
- return false;
- }
-
- // Now save specific stuff
- return create_object_entity($this->get('guid'), $this->get('title'), $this->get('description'), $this->get('container_guid'));
- }
-
- /**
- * Get sites that this object is a member of
- *
- * @param string $subtype Optionally, the subtype of result we want to limit to
- * @param int $limit The number of results to return
- * @param int $offset Any indexing offset
- */
- function getSites($subtype="", $limit = 10, $offset = 0) {
- return get_site_objects($this->getGUID(), $subtype, $limit, $offset);
- }
-
- /**
- * Add this object to a particular site
- *
- * @param int $site_guid The guid of the site to add it to
- * @return true|false
- */
- function addToSite($site_guid) {
- return add_site_object($this->getGUID(), $site_guid);
- }
-
- /**
- * Set the container for this object.
- *
- * @param int $container_guid The ID of the container.
- * @return bool
- */
- function setContainer($container_guid) {
- $container_guid = (int)$container_guid;
-
- return $this->set('container_guid', $container_guid);
- }
-
- /**
- * Return the container GUID of this object.
- *
- * @return int
- */
- function getContainer() {
- return $this->get('container_guid');
- }
-
- /**
- * As getContainer(), but returns the whole entity.
- *
- * @return mixed ElggGroup object or false.
- */
- function getContainerEntity() {
- $result = get_entity($this->getContainer());
-
- if (($result) && ($result instanceof ElggGroup)) {
- return $result;
- }
-
- return false;
- }
-
- /**
- * Get the collections associated with a object.
- *
- * @param string $subtype Optionally, the subtype of result we want to limit to
- * @param int $limit The number of results to return
- * @param int $offset Any indexing offset
- * @return unknown
- */
- //public function getCollections($subtype="", $limit = 10, $offset = 0) { get_object_collections($this->getGUID(), $subtype, $limit, $offset); }
-
- // EXPORTABLE INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Return an array of fields which can be exported.
- */
- public function getExportableValues() {
- return array_merge(parent::getExportableValues(), array(
- 'title',
- 'description',
- ));
- }
-}
\ No newline at end of file +<?php +/** + * Elgg Object + * + * Elgg objects are the most common means of storing information in the database. + * They are a child class of ElggEntity, so receive all the benefits of the Entities, + * but also include a title and description field. + * + * An ElggObject represents a row from the objects_entity table, as well + * as the related row in the entities table as represented by the parent + * ElggEntity object. + * + * @internal Title and description are stored in the objects_entity table. + * + * @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. + * + * @return void + */ + protected function initializeAttributes() { + parent::initializeAttributes(); + + $this->attributes['type'] = "object"; + $this->attributes['title'] = NULL; + $this->attributes['description'] = NULL; + $this->attributes['tables_split'] = 2; + } + + /** + * Load or create a new ElggObject. + * + * If no arguments are passed, create a new entity. + * + * 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 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 + * load the rest of the data. + * + * @throws IOException If passed an incorrect guid + * @throws InvalidParameterException If passed an Elgg* Entity that isn't an ElggObject + */ + function __construct($guid = null) { + $this->initializeAttributes(); + + // compatibility for 1.7 api. + $this->initialise_attributes(false); + + if (!empty($guid)) { + // Is $guid is a DB row from the entity table + if ($guid instanceof stdClass) { + // Load the rest + if (!$this->load($guid)) { + $msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid)); + throw new IOException($msg); + } + } 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; + } + } else if ($guid instanceof ElggEntity) { + // @todo remove - do not need separate exception + throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggObject')); + } else if (is_numeric($guid)) { + // $guid is a GUID so load + if (!$this->load($guid)) { + throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid))); + } + } else { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); + } + } + } + + /** + * Loads the full ElggObject when given a guid. + * + * @param mixed $guid GUID of an ElggObject or the stdClass object from entities table + * + * @return bool + * @throws InvalidClassException + */ + protected function load($guid) { + $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'; + + $attrs = $attr_loader->getRequiredAttributes($guid); + if (!$attrs) { + return false; + } + + $this->attributes = $attrs; + $this->attributes['tables_loaded'] = 2; + _elgg_cache_entity($this); + + return true; + } + + /** + * Saves object-specific attributes. + * + * @internal Object attributes are saved in the objects_entity table. + * + * @return bool + */ + public function save() { + // Save ElggEntity attributes + if (!parent::save()) { + return false; + } + + // Save ElggObject-specific attributes + + _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; + } + + /** + * Return sites that this object is a member of + * + * Site membership is determined by relationships and not site_guid.d + * + * @todo This should be moved to ElggEntity + * @todo Unimplemented + * + * @param string $subtype Optionally, the subtype of result we want to limit to + * @param int $limit The number of results to return + * @param int $offset Any indexing offset + * + * @return array|false + */ + function getSites($subtype = "", $limit = 10, $offset = 0) { + return get_site_objects($this->getGUID(), $subtype, $limit, $offset); + } + + /** + * Add this object to a site + * + * @param int $site_guid The guid of the site to add it to + * + * @return bool + */ + function addToSite($site_guid) { + return add_site_object($this->getGUID(), $site_guid); + } + + /* + * EXPORTABLE INTERFACE + */ + + /** + * Return an array of fields which can be exported. + * + * @return array + */ + public function getExportableValues() { + return array_merge(parent::getExportableValues(), array( + 'title', + 'description', + )); + } + + /** + * 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 921665f4d..545b9a53c 100644 --- a/engine/classes/ElggPlugin.php +++ b/engine/classes/ElggPlugin.php @@ -1,56 +1,1006 @@ -<?php
-/**
- * @class ElggPlugin Object representing a plugin's settings for a given site.
- * This class is currently a stub, allowing a plugin to saving settings in an object's metadata for each site.
- * @author Curverider Ltd
- */
-class ElggPlugin extends ElggObject {
- protected function initialise_attributes() {
- parent::initialise_attributes();
-
- $this->attributes['subtype'] = "plugin";
- }
-
- public function __construct($guid = null) {
- parent::__construct($guid);
- }
-
- /**
- * Override entity get and sets in order to save data to private data store.
- */
- public function get($name) {
- // See if its in our base attribute
- if (isset($this->attributes[$name])) {
- 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);
-
- if ($meta === false) {
- // Can't find it, so return null
- return NULL;
- }
-
- return $meta;
- }
-
- /**
- * Override entity get and sets in order to save data to private data store.
- */
- public function set($name, $value) {
- if (array_key_exists($name, $this->attributes)) {
- // Check that we're not trying to change the guid!
- if ((array_key_exists('guid', $this->attributes)) && ($name=='guid')) {
- return false;
- }
-
- $this->attributes[$name] = $value;
- } else {
- return set_private_setting($this->guid, $name, $value);
- }
-
- return true;
- }
-}
\ No newline at end of file +<?php +/** + * Stores site-side plugin settings as private data. + * + * This class is currently a stub, allowing a plugin to + * save settings in an object's private settings for each site. + * + * @package Elgg.Core + * @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 initializeAttributes() { + parent::initializeAttributes(); + + $this->attributes['subtype'] = "plugin"; + + // plugins must be public. + $this->access_id = ACCESS_PUBLIC; + } + + /** + * Loads the plugin by GUID or path. + * + * @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 + */ + public function __construct($plugin) { + if (!$plugin) { + throw new PluginException(elgg_echo('PluginException:NullInstantiated')); + } + + // 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 + * + * @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 (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 = $this->getPrivateSetting($name); + + if ($meta === false) { + // Can't find it, so return null + return NULL; + } + + return $meta; + } + + /** + * Save a value as private setting or attribute. + * + * Attributes include title and description. + * + * @param string $name Name + * @param mixed $value Value + * + * @return bool + */ + public function set($name, $value) { + if (array_key_exists($name, $this->attributes)) { + // Check that we're not trying to change the guid! + if ((array_key_exists('guid', $this->attributes)) && ($name == 'guid')) { + return false; + } + + $this->attributes[$name] = $value; + + return true; + } else { + // 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; + } + + 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; + } +} 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 4d14941a9..d2e88882a 100644 --- a/engine/classes/ElggRelationship.php +++ b/engine/classes/ElggRelationship.php @@ -1,287 +1,231 @@ -<?php
-
-/**
- * Relationship class.
- *
- * @author Curverider Ltd
- * @package Elgg
- * @subpackage Core
- */
-class ElggRelationship implements
- Importable,
- Exportable,
- Loggable, // Can events related to this object class be logged
- Iterator, // Override foreach behaviour
- ArrayAccess // Override for array access
- {
- /**
- * This contains the site's main properties (id, etc)
- * @var array
- */
- protected $attributes;
-
- /**
- * Construct a new site object, optionally from a given id value or row.
- *
- * @param mixed $id
- */
- function __construct($id = null) {
- $this->attributes = array();
-
- if (!empty($id)) {
- if ($id instanceof stdClass) {
- $relationship = $id; // Create from db row
- } else {
- $relationship = get_relationship($id);
- }
-
- if ($relationship) {
- $objarray = (array) $relationship;
- foreach($objarray as $key => $value) {
- $this->attributes[$key] = $value;
- }
- }
- }
- }
-
- /**
- * Class member get overloading
- *
- * @param string $name
- * @return mixed
- */
- function __get($name) {
- if (isset($this->attributes[$name])) {
- return $this->attributes[$name];
- }
-
- return null;
- }
-
- /**
- * Class member set overloading
- *
- * @param string $name
- * @param mixed $value
- * @return mixed
- */
- function __set($name, $value) {
- $this->attributes[$name] = $value;
- return true;
- }
-
- /**
- * Save the relationship
- *
- * @return int the relationship id
- */
- public function save() {
- if ($this->id > 0) {
- delete_relationship($this->id);
- }
-
- $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()));
- }
-
- return $this->id;
- }
-
- /**
- * Delete a given relationship.
- */
- public function delete() {
- return delete_relationship($this->id);
- }
-
- /**
- * Get a URL for this relationship.
- *
- * @return string
- */
- public function getURL() {
- return get_relationship_url($this->id);
- }
-
- // EXPORTABLE INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Return an array of fields which can be exported.
- */
- public function getExportableValues() {
- return array(
- 'id',
- 'guid_one',
- 'relationship',
- 'guid_two'
- );
- }
-
- /**
- * Export this relationship
- *
- * @return array
- */
- public function export() {
- $uuid = get_uuid_from_object($this);
- $relationship = new ODDRelationship(
- guid_to_uuid($this->guid_one),
- $this->relationship,
- guid_to_uuid($this->guid_two)
- );
-
- $relationship->setAttribute('uuid', $uuid);
-
- return $relationship;
- }
-
- // IMPORTABLE INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Import a relationship
- *
- * @param array $data
- * @param int $version
- * @return ElggRelationship
- * @throws ImportException
- */
- public function import(ODD $data) {
- if (!($element instanceof ODDRelationship)) {
- throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnexpectedODDClass'));
- }
-
- $uuid_one = $data->getAttribute('uuid1');
- $uuid_two = $data->getAttribute('uuid2');
-
- // See if this entity has already been imported, if so then we need to link to it
- $entity1 = get_entity_from_uuid($uuid_one);
- $entity2 = get_entity_from_uuid($uuid_two);
- if (($entity1) && ($entity2)) {
- // Set the item ID
- $this->attributes['guid_one'] = $entity1->getGUID();
- $this->attributes['guid_two'] = $entity2->getGUID();
-
- // Map verb to relationship
- //$verb = $data->getAttribute('verb');
- //$relationship = get_relationship_from_verb($verb);
- $relationship = $data->getAttribute('type');
-
- if ($relationship) {
- $this->attributes['relationship'] = $relationship;
- // save
- $result = $this->save();
- if (!$result) {
- throw new ImportException(sprintf(elgg_echo('ImportException:ProblemSaving'), get_class()));
- }
-
- return $this;
- }
- }
- }
-
- // SYSTEM LOG INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Return an identification for the object for storage in the system log.
- * This id must be an integer.
- *
- * @return int
- */
- public function getSystemLogID() {
- return $this->id;
- }
-
- /**
- * Return the class name of the object.
- */
- public function getClassName() {
- return get_class($this);
- }
-
- /**
- * For a given ID, return the object associated with it.
- * This is used by the river functionality primarily.
- * This is useful for checking access permissions etc on objects.
- */
- public function getObjectFromID($id) {
- return get_relationship($id);
- }
-
- /**
- * Return the GUID of the owner of this object.
- */
- public function getObjectOwnerGUID() {
- return $this->owner_guid;
- }
-
- /**
- * Return a type of the object - eg. object, group, user, relationship, metadata, annotation etc
- */
- public function getType() {
- return 'relationship';
- }
-
- /**
- * Return a subtype. For metadata & annotations this is the 'name' and for relationship this is the relationship type.
- */
- public function getSubtype() {
- return $this->relationship;
- }
-
- // ITERATOR INTERFACE //////////////////////////////////////////////////////////////
- /*
- * This lets an entity's attributes be displayed using foreach as a normal array.
- * Example: http://www.sitepoint.com/print/php5-standard-library
- */
-
- private $valid = FALSE;
-
- function rewind() {
- $this->valid = (FALSE !== reset($this->attributes));
- }
-
- function current() {
- return current($this->attributes);
- }
-
- function key() {
- return key($this->attributes);
- }
-
- function next() {
- $this->valid = (FALSE !== next($this->attributes));
- }
-
- function valid() {
- return $this->valid;
- }
-
- // ARRAY ACCESS INTERFACE //////////////////////////////////////////////////////////
- /*
- * This lets an entity's attributes be accessed like an associative array.
- * Example: http://www.sitepoint.com/print/php5-standard-library
- */
-
- function offsetSet($key, $value) {
- if ( array_key_exists($key, $this->attributes) ) {
- $this->attributes[$key] = $value;
- }
- }
-
- function offsetGet($key) {
- if ( array_key_exists($key, $this->attributes) ) {
- return $this->attributes[$key];
- }
- }
-
- function offsetUnset($key) {
- if ( array_key_exists($key, $this->attributes) ) {
- $this->attributes[$key] = ""; // Full unsetting is dangerious for our objects
- }
- }
-
- function offsetExists($offset) {
- return array_key_exists($offset, $this->attributes);
- }
-}
\ No newline at end of file +<?php +/** + * Relationship class. + * + * @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 +{ + + /** + * Create a relationship object, optionally from a given id value or row. + * + * @param mixed $id ElggRelationship id, database row, or null for new relationship + */ + function __construct($id = null) { + $this->initializeAttributes(); + + if (!empty($id)) { + if ($id instanceof stdClass) { + $relationship = $id; // Create from db row + } else { + $relationship = get_relationship($id); + } + + if ($relationship) { + $objarray = (array) $relationship; + foreach ($objarray as $key => $value) { + $this->attributes[$key] = $value; + } + } + } + } + + /** + * Class member get overloading + * + * @param string $name Name + * + * @return mixed + */ + function get($name) { + if (array_key_exists($name, $this->attributes)) { + return $this->attributes[$name]; + } + + return null; + } + + /** + * Class member set overloading + * + * @param string $name Name + * @param mixed $value Value + * + * @return mixed + */ + function set($name, $value) { + $this->attributes[$name] = $value; + return true; + } + + /** + * Save the relationship + * + * @return int the relationship id + * @throws IOException + */ + public function save() { + if ($this->id > 0) { + delete_relationship($this->id); + } + + $this->id = add_entity_relationship($this->guid_one, $this->relationship, $this->guid_two); + if (!$this->id) { + throw new IOException(elgg_echo('IOException:UnableToSaveNew', array(get_class()))); + } + + return $this->id; + } + + /** + * Delete a given relationship. + * + * @return bool + */ + public function delete() { + return delete_relationship($this->id); + } + + /** + * Get a URL for this relationship. + * + * @return string + */ + public function getURL() { + return get_relationship_url($this->id); + } + + // EXPORTABLE INTERFACE //////////////////////////////////////////////////////////// + + /** + * Return an array of fields which can be exported. + * + * @return array + */ + public function getExportableValues() { + return array( + 'id', + 'guid_one', + 'relationship', + 'guid_two' + ); + } + + /** + * Export this relationship + * + * @return array + */ + public function export() { + $uuid = get_uuid_from_object($this); + $relationship = new ODDRelationship( + guid_to_uuid($this->guid_one), + $this->relationship, + guid_to_uuid($this->guid_two) + ); + + $relationship->setAttribute('uuid', $uuid); + + return $relationship; + } + + // IMPORTABLE INTERFACE //////////////////////////////////////////////////////////// + + /** + * Import a relationship + * + * @param ODD $data ODD data + + * @return bool + * @throws ImportException|InvalidParameterException + */ + public function import(ODD $data) { + if (!($data instanceof ODDRelationship)) { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnexpectedODDClass')); + } + + $uuid_one = $data->getAttribute('uuid1'); + $uuid_two = $data->getAttribute('uuid2'); + + // See if this entity has already been imported, if so then we need to link to it + $entity1 = get_entity_from_uuid($uuid_one); + $entity2 = get_entity_from_uuid($uuid_two); + if (($entity1) && ($entity2)) { + // Set the item ID + $this->attributes['guid_one'] = $entity1->getGUID(); + $this->attributes['guid_two'] = $entity2->getGUID(); + + // Map verb to relationship + //$verb = $data->getAttribute('verb'); + //$relationship = get_relationship_from_verb($verb); + $relationship = $data->getAttribute('type'); + + if ($relationship) { + $this->attributes['relationship'] = $relationship; + // save + $result = $this->save(); + if (!$result) { + throw new ImportException(elgg_echo('ImportException:ProblemSaving', array(get_class()))); + } + + return true; + } + } + + return false; + } + + // SYSTEM LOG INTERFACE //////////////////////////////////////////////////////////// + + /** + * Return an identification for the object for storage in the system log. + * This id must be an integer. + * + * @return int + */ + public function getSystemLogID() { + return $this->id; + } + + /** + * For a given ID, return the object associated with it. + * This is used by the river functionality primarily. + * This is useful for checking access permissions etc on objects. + * + * @param int $id ID + * + * @return ElggRelationship + */ + public function getObjectFromID($id) { + return get_relationship($id); + } + + /** + * Return a type of the object - eg. object, group, user, relationship, metadata, annotation etc + * + * @return string 'relationship' + */ + public function getType() { + return 'relationship'; + } + + /** + * Return a subtype. For metadata & annotations this is the 'name' and for relationship this + * is the relationship type. + * + * @return string + */ + public function getSubtype() { + return $this->relationship; + } + +} 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 874502579..9750f063e 100644 --- a/engine/classes/ElggSession.php +++ b/engine/classes/ElggSession.php @@ -1,95 +1,153 @@ -<?php
-/**
- * Magic session class.
- * This class is intended to extend the $_SESSION magic variable by providing an API hook
- * to plug in other values.
- *
- * Primarily this is intended to provide a way of supplying "logged in user" details without touching the session
- * (which can cause problems when accessed server side).
- *
- * If a value is present in the session then that value is returned, otherwise a plugin hook 'session:get', '$var' is called,
- * where $var is the variable being requested.
- *
- * Setting values will store variables in the session in the normal way.
- *
- * LIMITATIONS: You can not access multidimensional arrays
- *
- * This is EXPERIMENTAL.
- */
-class ElggSession implements ArrayAccess {
- /** Local cache of trigger retrieved variables */
- private static $__localcache;
-
- function __isset($key) {
- return $this->offsetExists($key);
- }
-
- /** Set a value, go straight to session. */
- function offsetSet($key, $value) {
- $_SESSION[$key] = $value;
- }
-
- /**
- * Get a variable from either the session, or if its not in the session attempt to get it from
- * an api call.
- */
- function offsetGet($key) {
- if (!ElggSession::$__localcache) {
- ElggSession::$__localcache = array();
- }
-
- if (isset($_SESSION[$key])) {
- return $_SESSION[$key];
- }
-
- if (isset(ElggSession::$__localcache[$key])) {
- return ElggSession::$__localcache[$key];
- }
-
- $value = NULL;
- $value = trigger_plugin_hook('session:get', $key, NULL, $value);
-
- ElggSession::$__localcache[$key] = $value;
-
- return ElggSession::$__localcache[$key];
- }
-
- /**
- * Unset a value from the cache and the session.
- */
- function offsetUnset($key) {
- unset(ElggSession::$__localcache[$key]);
- unset($_SESSION[$key]);
- }
-
- /**
- * Return whether the value is set in either the session or the cache.
- */
- function offsetExists($offset) {
- if (isset(ElggSession::$__localcache[$offset])) {
- return true;
- }
-
- if (isset($_SESSION[$offset])) {
- return true;
- }
-
- if ($this->offsetGet($offset)){
- return true;
- }
- }
-
-
- // Alias functions
- function get($key) {
- return $this->offsetGet($key);
- }
-
- function set($key, $value) {
- return $this->offsetSet($key, $value);
- }
-
- function del($key) {
- return $this->offsetUnset($key);
- }
-}
\ No newline at end of file +<?php +/** + * Magic session class. + * This class is intended to extend the $_SESSION magic variable by providing an API hook + * to plug in other values. + * + * Primarily this is intended to provide a way of supplying "logged in user" + * details without touching the session (which can cause problems when + * accessed server side). + * + * If a value is present in the session then that value is returned, otherwise + * a plugin hook 'session:get', '$var' is called, where $var is the variable + * being requested. + * + * Setting values will store variables in the session in the normal way. + * + * LIMITATIONS: You can not access multidimensional arrays + * + * @package Elgg.Core + * @subpackage Sessions + */ +class ElggSession implements ArrayAccess { + /** Local cache of trigger retrieved variables */ + private static $__localcache; + + /** + * Test if property is set either as an attribute or metadata. + * + * @param string $key The name of the attribute or metadata. + * + * @return bool + */ + function __isset($key) { + return $this->offsetExists($key); + } + + /** + * Set a value, go straight to session. + * + * @param string $key Name + * @param mixed $value Value + * + * @return void + */ + function offsetSet($key, $value) { + $_SESSION[$key] = $value; + } + + /** + * Get a variable from either the session, or if its not in the session + * attempt to get it from an api call. + * + * @see ArrayAccess::offsetGet() + * + * @param mixed $key Name + * + * @return mixed + */ + function offsetGet($key) { + if (!ElggSession::$__localcache) { + ElggSession::$__localcache = array(); + } + + if (isset($_SESSION[$key])) { + return $_SESSION[$key]; + } + + if (isset(ElggSession::$__localcache[$key])) { + return ElggSession::$__localcache[$key]; + } + + $value = NULL; + $value = elgg_trigger_plugin_hook('session:get', $key, NULL, $value); + + ElggSession::$__localcache[$key] = $value; + + return ElggSession::$__localcache[$key]; + } + + /** + * Unset a value from the cache and the session. + * + * @see ArrayAccess::offsetUnset() + * + * @param mixed $key Name + * + * @return void + */ + function offsetUnset($key) { + unset(ElggSession::$__localcache[$key]); + unset($_SESSION[$key]); + } + + /** + * Return whether the value is set in either the session or the cache. + * + * @see ArrayAccess::offsetExists() + * + * @param int $offset Offset + * + * @return bool + */ + function offsetExists($offset) { + if (isset(ElggSession::$__localcache[$offset])) { + return true; + } + + if (isset($_SESSION[$offset])) { + return true; + } + + if ($this->offsetGet($offset)) { + return true; + } + + return false; + } + + + /** + * Alias to ::offsetGet() + * + * @param string $key Name + * + * @return mixed + */ + function get($key) { + return $this->offsetGet($key); + } + + /** + * Alias to ::offsetSet() + * + * @param string $key Name + * @param mixed $value Value + * + * @return void + */ + function set($key, $value) { + $this->offsetSet($key, $value); + } + + /** + * Alias to offsetUnset() + * + * @param string $key Name + * + * @return void + */ + function del($key) { + $this->offsetUnset($key); + } +} diff --git a/engine/classes/ElggSharedMemoryCache.php b/engine/classes/ElggSharedMemoryCache.php index cae78943e..f5f11d2c7 100644 --- a/engine/classes/ElggSharedMemoryCache.php +++ b/engine/classes/ElggSharedMemoryCache.php @@ -1,34 +1,40 @@ -<?php
-/**
- * Shared memory cache description.
- * Extends ElggCache with functions useful to shared memory style caches (static variables, memcache etc)
- */
-abstract class ElggSharedMemoryCache extends ElggCache {
- /**
- * Namespace variable used to keep various bits of the cache
- * separate.
- *
- * @var string
- */
- private $namespace;
-
- /**
- * Set the namespace of this cache.
- * This is useful for cache types (like memcache or static variables) where there is one large
- * flat area of memory shared across all instances of the cache.
- *
- * @param string $namespace
- */
- public function setNamespace($namespace = "default") {
- $this->namespace = $namespace;
- }
-
- /**
- * Get the namespace currently defined.
- *
- * @return string
- */
- public function getNamespace() {
- return $this->namespace;
- }
-}
\ No newline at end of file +<?php +/** + * Shared memory cache description. + * Extends ElggCache with functions useful to shared memory + * style caches (static variables, memcache etc) + * + * @package Elgg.Core + * @subpackage Cache + */ +abstract class ElggSharedMemoryCache extends ElggCache { + /** + * Namespace variable used to keep various bits of the cache + * separate. + * + * @var string + */ + private $namespace; + + /** + * Set the namespace of this cache. + * This is useful for cache types (like memcache or static variables) where there is one large + * flat area of memory shared across all instances of the cache. + * + * @param string $namespace Namespace for cache + * + * @return void + */ + public function setNamespace($namespace = "default") { + $this->namespace = $namespace; + } + + /** + * Get the namespace currently defined. + * + * @return string + */ + public function getNamespace() { + return $this->namespace; + } +} diff --git a/engine/classes/ElggSite.php b/engine/classes/ElggSite.php index 77e26372e..dd996fe98 100644 --- a/engine/classes/ElggSite.php +++ b/engine/classes/ElggSite.php @@ -1,299 +1,455 @@ -<?php
-/**
- * ElggSite
- * Representation of a "site" in the system.
- * @author Curverider Ltd <info@elgg.com>
- * @package Elgg
- * @subpackage Core
- */
-class ElggSite extends ElggEntity {
- /**
- * Initialise the attributes array.
- * This is vital to distinguish between metadata and base parameters.
- *
- * Place your base parameters here.
- */
- protected function initialise_attributes() {
- parent::initialise_attributes();
-
- $this->attributes['type'] = "site";
- $this->attributes['name'] = "";
- $this->attributes['description'] = "";
- $this->attributes['url'] = "";
- $this->attributes['tables_split'] = 2;
- }
-
- /**
- * Construct a new site object, 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.
- * @throws Exception if there was a problem creating the site.
- */
- function __construct($guid = null) {
- $this->initialise_attributes();
-
- if (!empty($guid)) {
- // Is $guid is a DB row - either a entity row, or a site table row.
- if ($guid instanceof stdClass) {
- // Load the rest
- if (!$this->load($guid->guid)) {
- throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid));
- }
- }
-
- // Is $guid is an ElggSite? Use a copy constructor
- else if ($guid instanceof ElggSite) {
- 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) {
- throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggSite'));
- }
-
- // See if this is a URL
- else if (strpos($guid, "http") !== false) {
- $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)) {
- if (!$this->load($guid)) {
- throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid));
- }
- }
-
- else {
- throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue'));
- }
- }
- }
-
- /**
- * Override the load function.
- * This function will ensure that all data is loaded (were possible), so
- * if only part of the ElggSite is loaded, it'll load the rest.
- *
- * @param int $guid
- */
- 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']!='site') {
- throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class()));
- }
-
- // 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'] ++;
- }
-
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach($objarray as $key => $value) {
- $this->attributes[$key] = $value;
- }
-
- return true;
- }
-
- /**
- * Override the save function.
- */
- public function save() {
- // Save generic stuff
- if (!parent::save()) {
- return false;
- }
-
- // Now save specific stuff
- return create_site_entity($this->get('guid'), $this->get('name'), $this->get('description'), $this->get('url'));
- }
-
- /**
- * Delete this site.
- */
- public function delete() {
- global $CONFIG;
- if ($CONFIG->site->getGUID() == $this->guid) {
- throw new SecurityException('SecurityException:deletedisablecurrentsite');
- }
-
- return parent::delete();
- }
-
- /**
- * Disable override to add safety rail.
- *
- * @param unknown_type $reason
- */
- public function disable($reason = "") {
- global $CONFIG;
-
- if ($CONFIG->site->getGUID() == $this->guid) {
- throw new SecurityException('SecurityException:deletedisablecurrentsite');
- }
-
- return parent::disable($reason);
- }
-
- /**
- * Return a list of users using this site.
- *
- * @param int $limit
- * @param int $offset
- * @return array of ElggUsers
- */
- public function getMembers($limit = 10, $offset = 0) {
- get_site_members($this->getGUID(), $limit, $offset);
- }
-
- /**
- * Add a user to the site.
- *
- * @param int $user_guid
- */
- public function addUser($user_guid) {
- return add_site_user($this->getGUID(), $user_guid);
- }
-
- /**
- * Remove a site user.
- *
- * @param int $user_guid
- */
- public function removeUser($user_guid) {
- return remove_site_user($this->getGUID(), $user_guid);
- }
-
- /**
- * Get an array of member ElggObjects.
- *
- * @param string $subtype
- * @param int $limit
- * @param int $offset
- */
- public function getObjects($subtype="", $limit = 10, $offset = 0) {
- get_site_objects($this->getGUID(), $subtype, $limit, $offset);
- }
-
- /**
- * Add an object to the site.
- *
- * @param int $user_id
- */
- public function addObject($object_guid) {
- return add_site_object($this->getGUID(), $object_guid);
- }
-
- /**
- * Remove a site user.
- *
- * @param int $user_id
- */
- public function removeObject($object_guid) {
- return remove_site_object($this->getGUID(), $object_guid);
- }
-
- /**
- * Get the collections associated with a site.
- *
- * @param string $type
- * @param int $limit
- * @param int $offset
- * @return unknown
- */
- public function getCollections($subtype="", $limit = 10, $offset = 0) {
- get_site_collections($this->getGUID(), $subtype, $limit, $offset);
- }
-
- // EXPORTABLE INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Return an array of fields which can be exported.
- */
- public function getExportableValues() {
- return array_merge(parent::getExportableValues(), array(
- 'name',
- 'description',
- 'url',
- ));
- }
-
- public function check_walled_garden() {
- 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);
-
- if (!$this->is_public_page()) {
- register_error(elgg_echo('loggedinrequired'));
- forward();
- }
- }
- }
-
- public function is_public_page($url='') {
- global $CONFIG;
-
- if (empty($url)) {
- $url = current_page_url();
-
- // do not check against URL queries
- if ($pos = strpos($url, '?')) {
- $url = substr($url, 0, $pos);
- }
- }
-
- // always allow index page
- if ($url == $CONFIG->url) {
- return TRUE;
- }
-
- // default public pages
- $defaults = array(
- 'action/login',
- 'pg/register',
- 'action/register',
- 'account/forgotten_password\.php',
- 'action/user/requestnewpassword',
- 'pg/resetpassword',
- 'upgrade\.php',
- 'xml-rpc\.php',
- 'mt/mt-xmlrpc\.cgi',
- '_css/css\.css',
- '_css/js\.php',
- );
-
- // 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
-
- // allow public pages
- foreach (array_merge($defaults, $plugins) as $public) {
- $pattern = "`^{$CONFIG->url}$public/*$`i";
- if (preg_match($pattern, $url)) {
- return TRUE;
- }
- }
-
- // non-public page
- return FALSE;
- }
-}
+<?php +/** + * A Site entity. + * + * ElggSite represents a single site entity. + * + * An ElggSite object is an ElggEntity child class with the subtype + * of "site." It is created upon installation and hold all the + * information about a site: + * - name + * - description + * - url + * + * Every ElggEntity (except ElggSite) belongs to a site. + * + * @internal ElggSite represents a single row from the sites_entity + * table, as well as the corresponding ElggEntity row from the entities table. + * + * @warning Multiple site support isn't fully developed. + * + * @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. + * + * @return void + */ + protected function initializeAttributes() { + parent::initializeAttributes(); + + $this->attributes['type'] = "site"; + $this->attributes['name'] = NULL; + $this->attributes['description'] = NULL; + $this->attributes['url'] = NULL; + $this->attributes['tables_split'] = 2; + } + + /** + * Load or create a new ElggSite. + * + * If no arguments are passed, create a new entity. + * + * If an argument is passed attempt to load a full Site entity. Arguments + * can be: + * - The GUID of a site entity. + * - 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 + * 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 + */ + function __construct($guid = null) { + $this->initializeAttributes(); + + // compatibility for 1.7 api. + $this->initialise_attributes(false); + + if (!empty($guid)) { + // Is $guid is a DB entity table row + if ($guid instanceof stdClass) { + // Load the rest + if (!$this->load($guid)) { + $msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid)); + throw new IOException($msg); + } + } 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; + } + } else if ($guid instanceof ElggEntity) { + // @todo remove and just use else clause + throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggSite')); + } 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; + } + } else if (is_numeric($guid)) { + // $guid is a GUID so load + if (!$this->load($guid)) { + throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid))); + } + } else { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); + } + } + } + + /** + * Loads the full ElggSite when given a guid. + * + * @param mixed $guid GUID of ElggSite entity or database row object + * + * @return bool + * @throws InvalidClassException + */ + protected function load($guid) { + $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'; + + $attrs = $attr_loader->getRequiredAttributes($guid); + if (!$attrs) { + return false; + } + + $this->attributes = $attrs; + $this->attributes['tables_loaded'] = 2; + _elgg_cache_entity($this); + + return true; + } + + /** + * Saves site-specific attributes. + * + * @internal Site attributes are saved in the sites_entity table. + * + * @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')); + } + + /** + * Delete the site. + * + * @note You cannot delete the current site. + * + * @return bool + * @throws SecurityException + */ + public function delete() { + global $CONFIG; + if ($CONFIG->site->getGUID() == $this->guid) { + throw new SecurityException('SecurityException:deletedisablecurrentsite'); + } + + return parent::delete(); + } + + /** + * Disable the site + * + * @note You cannot disable the current site. + * + * @param string $reason Optional reason for disabling + * @param bool $recursive Recursively disable all contained entities? + * + * @return bool + * @throws SecurityException + */ + public function disable($reason = "", $recursive = true) { + global $CONFIG; + + if ($CONFIG->site->getGUID() == $this->guid) { + throw new SecurityException('SecurityException:deletedisablecurrentsite'); + } + + return parent::disable($reason, $recursive); + } + + /** + * 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 + * + * @todo remove $offset in 2.0 + * + * @return array of ElggUsers + */ + 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); + } + + /** + * Adds a user to the site. + * + * @param int $user_guid GUID + * + * @return bool + */ + public function addUser($user_guid) { + return add_site_user($this->getGUID(), $user_guid); + } + + /** + * Removes a user from the site. + * + * @param int $user_guid GUID + * + * @return bool + */ + public function removeUser($user_guid) { + return remove_site_user($this->getGUID(), $user_guid); + } + + /** + * 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 + * + * @return array + */ + public function getObjects($subtype = "", $limit = 10, $offset = 0) { + return get_site_objects($this->getGUID(), $subtype, $limit, $offset); + } + + /** + * Adds an object to the site. + * + * @param int $object_guid GUID + * + * @return bool + */ + public function addObject($object_guid) { + return add_site_object($this->getGUID(), $object_guid); + } + + /** + * Remvoes an object from the site. + * + * @param int $object_guid GUID + * + * @return bool + */ + public function removeObject($object_guid) { + return remove_site_object($this->getGUID(), $object_guid); + } + + /** + * Get the collections associated with a site. + * + * @param string $subtype Subtype + * @param int $limit Limit + * @param int $offset Offset + * + * @return unknown + * @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); + } + + /* + * EXPORTABLE INTERFACE + */ + + /** + * Return an array of fields which can be exported. + * + * @return array + */ + public function getExportableValues() { + return array_merge(parent::getExportableValues(), array( + 'name', + 'description', + 'url', + )); + } + + /** + * Halts bootup and redirects to the site front page + * if site is in walled garden mode, no user is logged in, + * and the URL is not a public page. + * + * @link http://docs.elgg.org/Tutorials/WalledGarden + * + * @return void + * @since 1.8.0 + */ + public function checkWalledGarden() { + global $CONFIG; + + // command line calls should not invoke the walled garden check + if (PHP_SAPI === 'cli') { + return; + } + + 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(); + } + } + } + } + + /** + * Returns if a URL is public for this site when in Walled Garden mode. + * + * Pages are registered to be public by {@elgg_plugin_hook public_pages walled_garden}. + * + * @param string $url Defaults to the current URL. + * + * @return bool + * @since 1.8.0 + */ + public function isPublicPage($url = '') { + global $CONFIG; + + if (empty($url)) { + $url = current_page_url(); + + // do not check against URL queries + if ($pos = strpos($url, '?')) { + $url = substr($url, 0, $pos); + } + } + + // always allow index page + if ($url == elgg_get_site_url($this->guid)) { + return TRUE; + } + + // default public pages + $defaults = array( + 'walled_garden/.*', + 'login', + 'action/login', + 'register', + 'action/register', + 'forgotpassword', + 'resetpassword', + 'action/user/requestnewpassword', + 'action/user/passwordreset', + 'action/security/refreshtoken', + 'ajax/view/js/languages', + 'upgrade\.php', + 'xml-rpc\.php', + 'mt/mt-xmlrpc\.cgi', + 'css/.*', + 'js/.*', + 'cache/css/.*', + 'cache/js/.*', + 'cron/.*', + 'services/.*', + ); + + // include a hook for plugin authors to include public pages + $plugins = elgg_trigger_plugin_hook('public_pages', 'walled_garden', NULL, array()); + + // allow public pages + foreach (array_merge($defaults, $plugins) as $public) { + $pattern = "`^{$CONFIG->url}$public/*$`i"; + if (preg_match($pattern, $url)) { + return TRUE; + } + } + + // non-public page + return FALSE; + } +} diff --git a/engine/classes/ElggStaticVariableCache.php b/engine/classes/ElggStaticVariableCache.php index 0038862bd..9c14fdfba 100644 --- a/engine/classes/ElggStaticVariableCache.php +++ b/engine/classes/ElggStaticVariableCache.php @@ -1,66 +1,96 @@ -<?php
-/**
- * ElggStaticVariableCache
- * Dummy cache which stores values in a static array. Using this makes future replacements to other caching back
- * ends (eg memcache) much easier.
- *
- * @author Curverider Ltd <info@elgg.com>
- * @package Elgg
- * @subpackage API
- */
-class ElggStaticVariableCache extends ElggSharedMemoryCache {
- /**
- * The cache.
- *
- * @var unknown_type
- */
- private static $__cache;
-
- /**
- * Create the variable cache.
- *
- * 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!
- */
- function __construct($namespace = 'default') {
- $this->setNamespace($namespace);
- $this->clear();
- }
-
- public function save($key, $data) {
- $namespace = $this->getNamespace();
-
- ElggStaticVariableCache::$__cache[$namespace][$key] = $data;
-
- return true;
- }
-
- public function load($key, $offset = 0, $limit = null) {
- $namespace = $this->getNamespace();
-
- if (isset(ElggStaticVariableCache::$__cache[$namespace][$key])) {
- return ElggStaticVariableCache::$__cache[$namespace][$key];
- }
-
- return false;
- }
-
- public function delete($key) {
- $namespace = $this->getNamespace();
-
- unset(ElggStaticVariableCache::$__cache[$namespace][$key]);
-
- return true;
- }
-
- public function clear() {
- $namespace = $this->getNamespace();
-
- if (!isset(ElggStaticVariableCache::$__cache)) {
- ElggStaticVariableCache::$__cache = array();
- }
-
- ElggStaticVariableCache::$__cache[$namespace] = array();
- }
-}
\ No newline at end of file +<?php +/** + * ElggStaticVariableCache + * Dummy cache which stores values in a static array. Using this makes future + * replacements to other caching back ends (eg memcache) much easier. + * + * @package Elgg.Core + * @subpackage Cache + */ +class ElggStaticVariableCache extends ElggSharedMemoryCache { + /** + * The cache. + * + * @var array + */ + private static $__cache; + + /** + * Create the variable cache. + * + * 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. + * @warning namespaces of the same name are shared! + */ + function __construct($namespace = 'default') { + $this->setNamespace($namespace); + $this->clear(); + } + + /** + * Save a key + * + * @param string $key Name + * @param string $data Value + * + * @return boolean + */ + public function save($key, $data) { + $namespace = $this->getNamespace(); + + ElggStaticVariableCache::$__cache[$namespace][$key] = $data; + + return true; + } + + /** + * Load a key + * + * @param string $key Name + * @param int $offset Offset + * @param int $limit Limit + * + * @return string + */ + public function load($key, $offset = 0, $limit = null) { + $namespace = $this->getNamespace(); + + if (isset(ElggStaticVariableCache::$__cache[$namespace][$key])) { + return ElggStaticVariableCache::$__cache[$namespace][$key]; + } + + return false; + } + + /** + * Invalidate a given key. + * + * @param string $key Name + * + * @return bool + */ + public function delete($key) { + $namespace = $this->getNamespace(); + + unset(ElggStaticVariableCache::$__cache[$namespace][$key]); + + return true; + } + + /** + * Clears the cache for a particular namespace + * + * @return void + */ + public function clear() { + $namespace = $this->getNamespace(); + + if (!isset(ElggStaticVariableCache::$__cache)) { + ElggStaticVariableCache::$__cache = array(); + } + + ElggStaticVariableCache::$__cache[$namespace] = array(); + } +} 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 001da19ed..6163f9b62 100644 --- a/engine/classes/ElggUser.php +++ b/engine/classes/ElggUser.php @@ -1,427 +1,588 @@ -<?php
-/**
- * ElggUser
- *
- * Representation of a "user" in the system.
- *
- * @package Elgg
- * @subpackage Core
- */
-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.
- */
- protected function initialise_attributes() {
- parent::initialise_attributes();
-
- $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['banned'] = "no";
- $this->attributes['admin'] = 'no';
- $this->attributes['tables_split'] = 2;
- }
-
- /**
- * 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.
- * @throws Exception if there was a problem creating the user.
- */
- function __construct($guid = null) {
- $this->initialise_attributes();
-
- if (!empty($guid)) {
- // Is $guid is a DB row - either a entity row, or a user table row.
- if ($guid instanceof stdClass) {
- // Load the rest
- if (!$this->load($guid->guid)) {
- throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid->guid));
- }
- }
-
- // 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;
- }
- }
-
- // Is $guid is an ElggUser? Use a copy constructor
- else if ($guid instanceof ElggUser) {
- 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) {
- throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggUser'));
- }
-
- // We assume if we have got this far, $guid is an int
- else if (is_numeric($guid)) {
- if (!$this->load($guid)) {
- throw new IOException(sprintf(elgg_echo('IOException:FailedToLoadGUID'), get_class(), $guid));
- }
- }
-
- else {
- throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue'));
- }
- }
- }
-
- /**
- * 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.
- *
- * @param int $guid
- * @return true|false
- */
- 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']!='user') {
- throw new InvalidClassException(sprintf(elgg_echo('InvalidClassException:NotValidElggStar'), $guid, get_class()));
- }
-
- // 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'] ++;
- }
-
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach($objarray as $key => $value) {
- $this->attributes[$key] = $value;
- }
-
- return true;
- }
-
- /**
- * Saves this user to the database.
- * @return true|false
- */
- public function save() {
- // Save generic stuff
- if (!parent::save()) {
- return false;
- }
-
- // Now save specific stuff
- return 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'));
- }
-
- /**
- * User specific override of the entity delete method.
- *
- * @return bool
- */
- public function delete() {
- global $USERNAME_TO_GUID_MAP_CACHE, $CODE_TO_GUID_MAP_CACHE;
-
- // clear cache
- if (isset($USERNAME_TO_GUID_MAP_CACHE[$this->username])) {
- unset($USERNAME_TO_GUID_MAP_CACHE[$this->username]);
- }
- if (isset($CODE_TO_GUID_MAP_CACHE[$this->code])) {
- 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
- return parent::delete();
- }
-
- /**
- * Ban this user.
- *
- * @param string $reason Optional reason
- */
- public function ban($reason = "") {
- return ban_user($this->guid, $reason);
- }
-
- /**
- * Unban this user.
- */
- public function unban() {
- return unban_user($this->guid);
- }
-
- /**
- * Is this user banned or not?
- *
- * @return bool
- */
- public function isBanned() {
- return $this->banned == 'yes';
- }
-
- /**
- * Is this user admin?
- *
- * @return bool
- */
- public function isAdmin() {
-
- // for backward compatibility we need to pull this directly
- // from the attributes instead of using the magic methods.
- // this can be removed in 1.9
- // return $this->admin == 'yes';
- return $this->attributes['admin'] == 'yes';
- }
-
- /**
- * Make the user an admin
- *
- * @return bool
- */
- public function makeAdmin() {
- if (make_user_admin($this->guid)) {
- $this->attributes['admin'] = 'yes';
- return TRUE;
- }
- return FALSE;
- }
-
- /**
- * Remove the admin flag for user
- *
- * @return bool
- */
- public function removeAdmin() {
- if (remove_user_admin($this->guid)) {
- $this->attributes['admin'] = 'no';
- return TRUE;
- }
- return FALSE;
- }
-
- /**
- * Get sites that this user is a member of
- *
- * @param string $subtype Optionally, the subtype of result we want to limit to
- * @param int $limit The number of results to return
- * @param int $offset Any indexing offset
- */
- function getSites($subtype="", $limit = 10, $offset = 0) {
- // return get_site_users($this->getGUID(), $subtype, $limit, $offset);
- return get_user_sites($this->getGUID(), $subtype, $limit, $offset);
- }
-
- /**
- * Add this user to a particular site
- *
- * @param int $site_guid The guid of the site to add it to
- * @return true|false
- */
- function addToSite($site_guid) {
- // return add_site_user($this->getGUID(), $site_guid);
- return add_site_user($site_guid, $this->getGUID());
- }
-
- /**
- * Remove this user from a particular site
- *
- * @param int $site_guid The guid of the site to remove it from
- * @return true|false
- */
- function removeFromSite($site_guid) {
- //return remove_site_user($this->getGUID(), $site_guid);
- return remove_site_user($site_guid, $this->getGUID());
- }
-
- /**
- * Adds a user to this user's friends list
- *
- * @param int $friend_guid The GUID of the user to add
- * @return true|false Depending on success
- */
- function addFriend($friend_guid) {
- return user_add_friend($this->getGUID(), $friend_guid);
- }
-
- /**
- * Removes a user from this user's friends list
- *
- * @param int $friend_guid The GUID of the user to remove
- * @return true|false Depending on success
- */
- function removeFriend($friend_guid) {
- return user_remove_friend($this->getGUID(), $friend_guid);
- }
-
- /**
- * Determines whether or not this user is a friend of the currently logged in user
- *
- * @return true|false
- */
- function isFriend() {
- return user_is_friend(get_loggedin_userid(), $this->getGUID());
- }
-
- /**
- * 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
- * @return true|false
- */
- 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
- *
- * @param int $user_guid The GUID of the user to check against
- * @return true|false
- */
- function isFriendOf($user_guid) {
- return user_is_friend($user_guid, $this->getGUID());
- }
-
- /**
- * Retrieves a list of this user's friends
- *
- * @param string $subtype Optionally, the subtype of user to filter to (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
- */
- 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
- *
- * @param string $subtype Optionally, the subtype of user to filter to (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
- */
- 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.
- *
- * @param string $subtype The subtype of the objects, if any
- * @param int $limit Number of results to return
- * @param int $offset Any indexing offset
- */
- public function getObjects($subtype="", $limit = 10, $offset = 0) {
- return get_user_objects($this->getGUID(), $subtype, $limit, $offset);
- }
-
- /**
- * Get an array of ElggObjects owned by this user's friends.
- *
- * @param string $subtype The subtype of the objects, if any
- * @param int $limit Number of results to return
- * @param int $offset Any indexing offset
- */
- public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0) {
- return get_user_friends_objects($this->getGUID(), $subtype, $limit, $offset);
- }
-
- /**
- * Counts the number of ElggObjects owned by this user
- *
- * @param string $subtype The subtypes of the objects, if any
- * @return int The number of ElggObjects
- */
- public function countObjects($subtype = "") {
- return count_user_objects($this->getGUID(), $subtype);
- }
-
- /**
- * Get the collections associated with a user.
- *
- * @param string $subtype Optionally, the subtype of result we want to limit to
- * @param int $limit The number of results to return
- * @param int $offset Any indexing offset
- * @return unknown
- */
- public function getCollections($subtype="", $limit = 10, $offset = 0) {
- return get_user_collections($this->getGUID(), $subtype, $limit, $offset);
- }
-
- /**
- * If a user's owner is blank, return its own GUID as the owner
- *
- * @return int User GUID
- */
- function getOwner() {
- if ($this->owner_guid == 0) {
- return $this->getGUID();
- }
-
- return $this->owner_guid;
- }
-
- // EXPORTABLE INTERFACE ////////////////////////////////////////////////////////////
-
- /**
- * Return an array of fields which can be exported.
- */
- public function getExportableValues() {
- return array_merge(parent::getExportableValues(), array(
- 'name',
- 'username',
- 'language',
- ));
- }
-
- // backward compatibility with admin flag
- // remove for 1.9
- 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');
-
- if ($value == 'yes' || $value == '1') {
- $this->makeAdmin();
- } else {
- $this->removeAdmin();
- }
- }
- return parent::__set($name, $value);
- }
-
- 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');
- return $this->isAdmin();
- }
-
- return parent::__get($name);
- }
-}
+<?php +/** + * ElggUser + * + * Representation of a "user" in the system. + * + * @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. + * + * @return void + */ + protected function initializeAttributes() { + parent::initializeAttributes(); + + $this->attributes['type'] = "user"; + $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; + } + + /** + * Construct a new user entity, optionally from a given id value. + * + * @param mixed $guid If an int, load that GUID. + * 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 entity row + if ($guid instanceof stdClass) { + // Load the rest + if (!$this->load($guid)) { + $msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid)); + throw new IOException($msg); + } + } else if (is_string($guid)) { + // $guid is a username + $user = get_user_by_username($guid); + if ($user) { + foreach ($user->attributes as $key => $value) { + $this->attributes[$key] = $value; + } + } + } 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; + } + } else if ($guid instanceof ElggEntity) { + // @todo why have a special case here + throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggUser')); + } else if (is_numeric($guid)) { + // $guid is a GUID so load entity + if (!$this->load($guid)) { + throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid))); + } + } else { + throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnrecognisedValue')); + } + } + } + + /** + * Load the ElggUser data from the database + * + * @param mixed $guid ElggUser GUID or stdClass database row from entity table + * + * @return bool + */ + protected function load($guid) { + $attr_loader = new ElggAttributeLoader(get_class(), 'user', $this->attributes); + $attr_loader->secondary_loader = 'get_user_entity_as_row'; + + $attrs = $attr_loader->getRequiredAttributes($guid); + if (!$attrs) { + return false; + } + + $this->attributes = $attrs; + $this->attributes['tables_loaded'] = 2; + _elgg_cache_entity($this); + + return true; + } + + /** + * Saves this user to the database. + * + * @return bool + */ + public function save() { + // Save generic stuff + if (!parent::save()) { + return false; + } + + // Now save specific stuff + _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; + } + + /** + * User specific override of the entity delete method. + * + * @return bool + */ + public function delete() { + global $USERNAME_TO_GUID_MAP_CACHE, $CODE_TO_GUID_MAP_CACHE; + + // clear cache + if (isset($USERNAME_TO_GUID_MAP_CACHE[$this->username])) { + unset($USERNAME_TO_GUID_MAP_CACHE[$this->username]); + } + if (isset($CODE_TO_GUID_MAP_CACHE[$this->code])) { + unset($CODE_TO_GUID_MAP_CACHE[$this->code]); + } + + clear_user_files($this); + + // Delete entity + return parent::delete(); + } + + /** + * Ban this user. + * + * @param string $reason Optional reason + * + * @return bool + */ + public function ban($reason = "") { + return ban_user($this->guid, $reason); + } + + /** + * Unban this user. + * + * @return bool + */ + public function unban() { + return unban_user($this->guid); + } + + /** + * Is this user banned or not? + * + * @return bool + */ + public function isBanned() { + return $this->banned == 'yes'; + } + + /** + * Is this user admin? + * + * @return bool + */ + public function isAdmin() { + + // for backward compatibility we need to pull this directly + // from the attributes instead of using the magic methods. + // this can be removed in 1.9 + // return $this->admin == 'yes'; + return $this->attributes['admin'] == 'yes'; + } + + /** + * Make the user an admin + * + * @return bool + */ + public function makeAdmin() { + // If already saved, use the standard function. + if ($this->guid && !make_user_admin($this->guid)) { + return FALSE; + } + + // need to manually set attributes since they've already been loaded. + $this->attributes['admin'] = 'yes'; + + return TRUE; + } + + /** + * Remove the admin flag for user + * + * @return bool + */ + public function removeAdmin() { + // If already saved, use the standard function. + if ($this->guid && !remove_user_admin($this->guid)) { + return FALSE; + } + + // need to manually set attributes since they've already been loaded. + $this->attributes['admin'] = 'no'; + + return TRUE; + } + + /** + * Get sites that this user is a member of + * + * @param string $subtype Optionally, the subtype of result we want to limit to + * @param int $limit The number of results to return + * @param int $offset Any indexing offset + * + * @return array + */ + function getSites($subtype = "", $limit = 10, $offset = 0) { + return get_user_sites($this->getGUID(), $subtype, $limit, $offset); + } + + /** + * Add this user to a particular site + * + * @param int $site_guid The guid of the site to add it to + * + * @return bool + */ + function addToSite($site_guid) { + return add_site_user($site_guid, $this->getGUID()); + } + + /** + * Remove this user from a particular site + * + * @param int $site_guid The guid of the site to remove it from + * + * @return bool + */ + function removeFromSite($site_guid) { + return remove_site_user($site_guid, $this->getGUID()); + } + + /** + * Adds a user as a friend + * + * @param int $friend_guid The GUID of the user to add + * + * @return bool + */ + function addFriend($friend_guid) { + return user_add_friend($this->getGUID(), $friend_guid); + } + + /** + * Removes a user as a friend + * + * @param int $friend_guid The GUID of the user to remove + * + * @return bool + */ + function removeFriend($friend_guid) { + return user_remove_friend($this->getGUID(), $friend_guid); + } + + /** + * Determines whether or not this user is a friend of the currently logged in user + * + * @return bool + */ + function isFriend() { + 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 against + * + * @return bool + */ + function isFriendsWith($user_guid) { + return user_is_friend($this->getGUID(), $user_guid); + } + + /** + * Determines whether or not this user is another user's friend + * + * @param int $user_guid The GUID of the user to check against + * + * @return bool + */ + function isFriendOf($user_guid) { + return user_is_friend($user_guid, $this->getGUID()); + } + + /** + * Gets this user's friends + * + * @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 ElggUser, or false, depending on success + */ + function getFriends($subtype = "", $limit = 10, $offset = 0) { + return get_user_friends($this->getGUID(), $subtype, $limit, $offset); + } + + /** + * Gets users who have made this user a friend + * + * @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 ElggUser, or false, depending on success + */ + function getFriendsOf($subtype = "", $limit = 10, $offset = 0) { + return get_user_friends_of($this->getGUID(), $subtype, $limit, $offset); + } + + /** + * 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 + * @param int $offset Any indexing offset + * + * @return array|false + */ + public function getObjects($subtype = "", $limit = 10, $offset = 0) { + $params = array( + 'type' => 'object', + 'subtype' => $subtype, + 'owner_guid' => $this->getGUID(), + 'limit' => $limit, + 'offset' => $offset + ); + return elgg_get_entities($params); + } + + /** + * Get an array of ElggObjects owned by this user's friends. + * + * @param string $subtype The subtype of the objects, if any + * @param int $limit Number of results to return + * @param int $offset Any indexing offset + * + * @return array|false + */ + public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0) { + return get_user_friends_objects($this->getGUID(), $subtype, $limit, $offset); + } + + /** + * Counts the number of ElggObjects owned by this user + * + * @param string $subtype The subtypes of the objects, if any + * + * @return int The number of ElggObjects + */ + public function countObjects($subtype = "") { + return count_user_objects($this->getGUID(), $subtype); + } + + /** + * Get the collections associated with a user. + * + * @param string $subtype Optionally, the subtype of result we want to limit to + * @param int $limit The number of results to return + * @param int $offset Any indexing offset + * + * @return array|false + */ + public function getCollections($subtype = "", $limit = 10, $offset = 0) { + elgg_deprecated_notice("ElggUser::getCollections() has been deprecated", 1.8); + return false; + } + + /** + * Get a user's owner GUID + * + * Returns it's own GUID if the user is not owned. + * + * @return int + */ + function getOwnerGUID() { + if ($this->owner_guid == 0) { + 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 //////////////////////////////////////////////////////////// + + /** + * Return an array of fields which can be exported. + * + * @return array + */ + public function getExportableValues() { + return array_merge(parent::getExportableValues(), array( + 'name', + 'username', + 'language', + )); + } + + /** + * Need to catch attempts to make a user an admin. Remove for 1.9 + * + * @param string $name Name + * @param mixed $value Value + * + * @return bool + */ + 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); + + if ($value == 'yes' || $value == '1') { + $this->makeAdmin(); + } else { + $this->removeAdmin(); + } + } + return parent::__set($name, $value); + } + + /** + * Need to catch attempts to test user for admin. Remove for 1.9 + * + * @param string $name Name + * + * @return bool + */ + 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); + 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 dbca3c369..66191bf47 100644 --- a/engine/classes/ElggWidget.php +++ b/engine/classes/ElggWidget.php @@ -1,53 +1,245 @@ -<?php
-
-/**
- * Override ElggObject in order to store widget data in ultra-private stores.
- */
-class ElggWidget extends ElggObject {
- protected function initialise_attributes() {
- parent::initialise_attributes();
-
- $this->attributes['subtype'] = "widget";
- }
-
- public function __construct($guid = null) {
- parent::__construct($guid);
- }
-
- /**
- * Override entity get and sets in order to save data to private data store.
- */
- public function get($name) {
- // See if its in our base attribute
- if (isset($this->attributes[$name])) {
- return $this->attributes[$name];
- }
-
- // No, so see if its in the private data store.
- $meta = get_private_setting($this->guid, $name);
- if ($meta) {
- return $meta;
- }
-
- // Can't find it, so return null
- return null;
- }
-
- /**
- * Override entity get and sets in order to save data to private data store.
- */
- public function set($name, $value) {
- if (array_key_exists($name, $this->attributes)) {
- // Check that we're not trying to change the guid!
- if ((array_key_exists('guid', $this->attributes)) && ($name=='guid')) {
- return false;
- }
-
- $this->attributes[$name] = $value;
- } else {
- return set_private_setting($this->guid, $name, $value);
- }
-
- return true;
- }
-}
\ No newline at end of file +<?php + +/** + * 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. + * + * @return void + */ + protected function initializeAttributes() { + parent::initializeAttributes(); + + $this->attributes['subtype'] = "widget"; + } + + /** + * Override entity get and sets in order to save data to private data store. + * + * @param string $name Name + * + * @return mixed + */ + public function get($name) { + // See if its in our base attribute + if (array_key_exists($name, $this->attributes)) { + return $this->attributes[$name]; + } + + // No, so see if its in the private data store. + $meta = $this->getPrivateSetting($name); + if ($meta) { + return $meta; + } + + // Can't find it, so return null + return null; + } + + /** + * Override entity get and sets in order to save data to private data store. + * + * @param string $name Name + * @param string $value Value + * + * @return bool + */ + public function set($name, $value) { + if (array_key_exists($name, $this->attributes)) { + // Check that we're not trying to change the guid! + if ((array_key_exists('guid', $this->attributes)) && ($name == 'guid')) { + return false; + } + + $this->attributes[$name] = $value; + } else { + 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; + } +} 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 5fc6c88b6..afad4c740 100644 --- a/engine/classes/ErrorResult.php +++ b/engine/classes/ErrorResult.php @@ -1,44 +1,54 @@ -<?php
-/**
- * ErrorResult
- * The error result class.
- *
- * @author Curverider Ltd <info@elgg.com>
- * @package Elgg
- * @subpackage Core
- */
-class ErrorResult extends GenericResult {
- // Fail with no specific code
- public static $RESULT_FAIL = -1 ;
-
- public static $RESULT_FAIL_APIKEY_DISABLED = -30;
- public static $RESULT_FAIL_APIKEY_INACTIVE = -31;
- public static $RESULT_FAIL_APIKEY_INVALID = -32;
-
- // Invalid, expired or missing auth token
- public static $RESULT_FAIL_AUTHTOKEN = -20;
-
- public function ErrorResult($message, $code = "", Exception $exception = NULL) {
- if ($code == "") {
- $code = ErrorResult::$RESULT_FAIL;
- }
-
- if ($exception!=NULL) {
- $this->setResult($exception->__toString());
- }
-
- $this->setStatusCode($code, $message);
- }
-
- /**
- * Get a new instance of the ErrorResult.
- *
- * @param string $message
- * @param int $code
- * @param Exception $exception Optional exception for generating a stack trace.
- */
- public static function getInstance($message, $code = "", Exception $exception = NULL) {
- // Return a new error object.
- return new ErrorResult($message, $code, $exception);
- }
-}
\ No newline at end of file +<?php +/** + * ErrorResult + * The error result class. + * + * @package Elgg.Core + * @subpackage WebServicesAPI + */ +class ErrorResult extends GenericResult { + // Fail with no specific code + public static $RESULT_FAIL = -1 ; + + public static $RESULT_FAIL_APIKEY_DISABLED = -30; + public static $RESULT_FAIL_APIKEY_INACTIVE = -31; + public static $RESULT_FAIL_APIKEY_INVALID = -32; + + // Invalid, expired or missing auth token + public static $RESULT_FAIL_AUTHTOKEN = -20; + + /** + * A new error result + * + * @param string $message Message + * @param int $code Error Code + * @param Exception $exception Exception object + * + * @return void + */ + public function __construct($message, $code = "", Exception $exception = NULL) { + if ($code == "") { + $code = ErrorResult::$RESULT_FAIL; + } + + if ($exception != NULL) { + $this->setResult($exception->__toString()); + } + + $this->setStatusCode($code, $message); + } + + /** + * Get a new instance of the ErrorResult. + * + * @param string $message Message + * @param int $code Code + * @param Exception $exception Optional exception for generating a stack trace. + * + * @return ErrorResult + */ + public static function getInstance($message, $code = "", Exception $exception = NULL) { + // Return a new error object. + return new ErrorResult($message, $code, $exception); + } +} diff --git a/engine/classes/ExportException.php b/engine/classes/ExportException.php index dc5c686b7..ae8a8e41b 100644 --- a/engine/classes/ExportException.php +++ b/engine/classes/ExportException.php @@ -1,9 +1,9 @@ -<?php
-/**
- * Export exception
- *
- * @package Elgg
- * @subpackage Exceptions
- *
- */
-class ExportException extends DataFormatException {}
\ No newline at end of file +<?php +/** + * Export exception + * + * @package Elgg.Core + * @subpackage Exception + * + */ +class ExportException extends DataFormatException {} diff --git a/engine/classes/Exportable.php b/engine/classes/Exportable.php index da5a7cc54..0c1ea5282 100644 --- a/engine/classes/Exportable.php +++ b/engine/classes/Exportable.php @@ -1,21 +1,23 @@ -<?php
-/**
- * Define an interface for all ODD exportable objects.
- *
- * @package Elgg
- * @subpackage Core
- * @author Curverider Ltd
- */
-interface Exportable {
- /**
- * This must take the contents of the object and convert it to exportable ODD
- * @return object or array of objects.
- */
- public function export();
-
- /**
- * Return a list of all fields that can be exported.
- * This should be used as the basis for the values returned by export()
- */
- public function getExportableValues();
-}
\ No newline at end of file +<?php +/** + * Define an interface for all ODD exportable objects. + * + * @package Elgg.Core + * @subpackage ODD + */ +interface Exportable { + /** + * This must take the contents of the object and convert it to exportable ODD + * + * @return object or array of objects. + */ + public function export(); + + /** + * Return a list of all fields that can be exported. + * This should be used as the basis for the values returned by export() + * + * @return array + */ + public function getExportableValues(); +} diff --git a/engine/classes/Friendable.php b/engine/classes/Friendable.php index d400bd092..c308b4598 100644 --- a/engine/classes/Friendable.php +++ b/engine/classes/Friendable.php @@ -2,12 +2,16 @@ /** * An interface for objects that behave as elements within a social network that have a profile. * + * @package Elgg.Core + * @subpackage SocialModel.Friendable */ interface Friendable { /** * Adds a user as a friend * * @param int $friend_guid The GUID of the user to add + * + * @return bool */ public function addFriend($friend_guid); @@ -15,12 +19,15 @@ interface Friendable { * Removes a user as a friend * * @param int $friend_guid The GUID of the user to remove + * + * @return bool */ public function removeFriend($friend_guid); /** * Determines whether or not the current user is a friend of this entity * + * @return bool */ public function isFriend(); @@ -28,6 +35,8 @@ interface Friendable { * Determines whether or not this entity is friends with a particular entity * * @param int $user_guid The GUID of the entity this entity may or may not be friends with + * + * @return bool */ public function isFriendsWith($user_guid); @@ -35,6 +44,8 @@ interface Friendable { * Determines whether or not a foreign entity has made this one a friend * * @param int $user_guid The GUID of the foreign entity + * + * @return bool */ public function isFriendOf($user_guid); @@ -42,8 +53,10 @@ interface Friendable { * Returns this entity's friends * * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset + * @param int $limit The number of entities to return + * @param int $offset Indexing offset + * + * @return array|false */ public function getFriends($subtype = "", $limit = 10, $offset = 0); @@ -51,8 +64,10 @@ interface Friendable { * Returns entities that have made this entity a friend * * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset + * @param int $limit The number of entities to return + * @param int $offset Indexing offset + * + * @return array|false */ public function getFriendsOf($subtype = "", $limit = 10, $offset = 0); @@ -60,17 +75,21 @@ interface Friendable { * Returns objects in this entity's container * * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset + * @param int $limit The number of entities to return + * @param int $offset Indexing offset + * + * @return array|false */ - public function getObjects($subtype="", $limit = 10, $offset = 0); + public function getObjects($subtype = "", $limit = 10, $offset = 0); /** * Returns objects in the containers of this entity's friends * * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset + * @param int $limit The number of entities to return + * @param int $offset Indexing offset + * + * @return array|false */ public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0); @@ -78,6 +97,8 @@ interface Friendable { * Returns the number of object entities in this entity's container * * @param string $subtype The subtype of entity to count + * + * @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 8bccd77f2..e42e924d1 100644 --- a/engine/classes/GenericResult.php +++ b/engine/classes/GenericResult.php @@ -1,107 +1,125 @@ -<?php
-/**
- * GenericResult Result superclass.
- *
- * @author Curverider Ltd <info@elgg.com>
- * @package Elgg
- * @subpackage Core
- */
-abstract class GenericResult {
- /**
- * The status of the result.
- * @var int
- */
- private $status_code;
-
- /**
- * Message returned along with the status which is almost always an error message.
- * This must be human readable, understandable and localised.
- * @var string
- */
- private $message;
-
- /**
- * Result store.
- * Attach result specific informaton here.
- *
- * @var mixed. Should probably be an object of some sort.
- */
- private $result;
-
- /**
- * Set a status code and optional message.
- *
- * @param int $status The status code.
- * @param string $message The message.
- */
- protected function setStatusCode($status, $message = "") {
- $this->status_code = $status;
- $this->message = $message;
- }
-
- /**
- * Set the result.
- *
- * @param mixed $result
- */
- protected function setResult($result) {
- $this->result = $result;
- }
-
- protected function getStatusCode() {
- return $this->status_code;
- }
-
- protected function getStatusMessage() {
- return $this->message;
- }
-
- protected function getResult() {
- return $this->result;
- }
-
- /**
- * Serialise to a standard class.
- *
- * DEVNOTE: The API is only interested in data, we can not easily serialise
- * custom classes without the need for 1) the other side being PHP, 2) you need to have the class
- * definition installed, 3) its the right version!
- *
- * Therefore, I'm not bothering.
- *
- * Override this to include any more specific information, however api results should be attached to the
- * class using setResult().
- *
- * if $CONFIG->debug is set then additional information about the runtime environment and authentication will be
- * returned.
- *
- * @return stdClass Object containing the serialised result.
- */
- public function export() {
- global $ERRORS, $CONFIG, $_PAM_HANDLERS_MSG;
-
- $result = new stdClass;
-
- $result->status = $this->getStatusCode();
- if ($this->getStatusMessage()!="") {
- $result->message = $this->getStatusMessage();
- }
-
- $resultdata = $this->getResult();
- if (isset($resultdata)) {
- $result->result = $resultdata;
- }
-
- if (isset($CONFIG->debug)) {
- if (count($ERRORS)) {
- $result->runtime_errors = $ERRORS;
- }
-
- if (count($_PAM_HANDLERS_MSG)) {
- $result->pam = $_PAM_HANDLERS_MSG;
- }
- }
-
- return $result;
- }
-}
\ No newline at end of file +<?php +/** + * GenericResult Result superclass. + * + * @package Elgg.Core + * @subpackage WebServicesAPI + */ +abstract class GenericResult { + /** + * The status of the result. + * @var int + */ + private $status_code; + + /** + * Message returned along with the status which is almost always an error message. + * This must be human readable, understandable and localised. + * @var string + */ + private $message; + + /** + * Result store. + * Attach result specific informaton here. + * + * @var mixed. Should probably be an object of some sort. + */ + private $result; + + /** + * Set a status code and optional message. + * + * @param int $status The status code. + * @param string $message The message. + * + * @return void + */ + protected function setStatusCode($status, $message = "") { + $this->status_code = $status; + $this->message = $message; + } + + /** + * Set the result. + * + * @param mixed $result The result + * + * @return void + */ + protected function setResult($result) { + $this->result = $result; + } + + /** + * Return the current status code + * + * @return string + */ + protected function getStatusCode() { + return $this->status_code; + } + + /** + * Return the current status message + * + * @return string + */ + protected function getStatusMessage() { + return $this->message; + } + + /** + * Return the current result + * + * @return string + */ + protected function getResult() { + return $this->result; + } + + /** + * Serialise to a standard class. + * + * DEVNOTE: The API is only interested in data, we can not easily serialise + * custom classes without the need for 1) the other side being PHP, 2) you need to have the class + * definition installed, 3) its the right version! + * + * Therefore, I'm not bothering. + * + * Override this to include any more specific information, however api results + * should be attached to the class using setResult(). + * + * if $CONFIG->debug is set then additional information about the runtime environment and + * authentication will be returned. + * + * @return stdClass Object containing the serialised result. + */ + public function export() { + global $ERRORS, $CONFIG, $_PAM_HANDLERS_MSG; + + $result = new stdClass; + + $result->status = $this->getStatusCode(); + if ($this->getStatusMessage() != "") { + $result->message = $this->getStatusMessage(); + } + + $resultdata = $this->getResult(); + if (isset($resultdata)) { + $result->result = $resultdata; + } + + if (isset($CONFIG->debug)) { + if (count($ERRORS)) { + $result->runtime_errors = $ERRORS; + } + + if (count($_PAM_HANDLERS_MSG)) { + $result->pam = $_PAM_HANDLERS_MSG; + } + } + + return $result; + } +} diff --git a/engine/classes/IOException.php b/engine/classes/IOException.php new file mode 100644 index 000000000..57403f44c --- /dev/null +++ b/engine/classes/IOException.php @@ -0,0 +1,9 @@ +<?php +/** + * IOException + * An IO Exception, throw when an IO Exception occurs. Subclass for specific IO Exceptions. + * + * @package Elgg.Core + * @subpackage Exception + */ +class IOException extends Exception {} diff --git a/engine/classes/ImportException.php b/engine/classes/ImportException.php index fd86fc23c..909c599d5 100644 --- a/engine/classes/ImportException.php +++ b/engine/classes/ImportException.php @@ -1,8 +1,8 @@ -<?php
-/**
- * Import exception
- *
- * @package Elgg
- * @subpackage Exceptions
- */
-class ImportException extends DataFormatException {}
\ No newline at end of file +<?php +/** + * Import exception + * + * @package Elgg.Core + * @subpackage Exception + */ +class ImportException extends DataFormatException {} diff --git a/engine/classes/Importable.php b/engine/classes/Importable.php index 775319cb7..23b2ce2c8 100644 --- a/engine/classes/Importable.php +++ b/engine/classes/Importable.php @@ -1,16 +1,19 @@ -<?php
-/**
- * Define an interface for all ODD importable objects.
- * @author Curverider Ltd
- */
-interface Importable {
- /**
- * Accepts an array of data to import, this data is parsed from the XML produced by export.
- * The function should return the constructed object data, or NULL.
- *
- * @param ODD $data
- * @return bool
- * @throws ImportException if there was a critical error importing data.
- */
- public function import(ODD $data);
-}
+<?php +/** + * Define an interface for all ODD importable objects. + * + * @package Elgg.Core + * @subpackage DataModel.Importable + */ +interface Importable { + /** + * Accepts an array of data to import, this data is parsed from the XML produced by export. + * The function should return the constructed object data, or NULL. + * + * @param ODD $data Data in ODD format + * + * @return bool + * @throws ImportException if there was a critical error importing data. + */ + public function import(ODD $data); +} 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/InstallationException.php b/engine/classes/InstallationException.php new file mode 100644 index 000000000..1dad6c1e5 --- /dev/null +++ b/engine/classes/InstallationException.php @@ -0,0 +1,9 @@ +<?php +/** + * InstallationException + * Thrown when there is a major problem with the installation. + * + * @package Elgg.Core + * @subpackage Exception + */ +class InstallationException extends ConfigurationException {} diff --git a/engine/classes/InvalidClassException.php b/engine/classes/InvalidClassException.php new file mode 100644 index 000000000..12f353b9a --- /dev/null +++ b/engine/classes/InvalidClassException.php @@ -0,0 +1,9 @@ +<?php +/** + * InvalidClassException + * An invalid class Exception, throw when a class is invalid. + * + * @package Elgg.Core + * @subpackage Exception + */ +class InvalidClassException extends ClassException {} diff --git a/engine/classes/InvalidParameterException.php b/engine/classes/InvalidParameterException.php new file mode 100644 index 000000000..fbc9bffc9 --- /dev/null +++ b/engine/classes/InvalidParameterException.php @@ -0,0 +1,9 @@ +<?php +/** + * InvalidParameterException + * A parameter is invalid. + * + * @package Elgg.Core + * @subpackage Exception + */ +class InvalidParameterException extends CallException {} diff --git a/engine/classes/Locatable.php b/engine/classes/Locatable.php index 5f52d8eab..7287d9798 100644 --- a/engine/classes/Locatable.php +++ b/engine/classes/Locatable.php @@ -1,36 +1,49 @@ -<?php
-
-/**
- * Define an interface for geo-tagging entities.
- *
- */
-interface Locatable {
- /** Set a location text */
- public function setLocation($location);
-
- /**
- * Set latitude and longitude tags for a given entity.
- *
- * @param float $lat
- * @param float $long
- */
- public function setLatLong($lat, $long);
-
- /**
- * Get the contents of the ->geo:lat field.
- *
- */
- public function getLatitude();
-
- /**
- * Get the contents of the ->geo:lat field.
- *
- */
- public function getLongitude();
-
- /**
- * Get the ->location metadata.
- *
- */
- public function getLocation();
-}
\ No newline at end of file +<?php + +/** + * Define an interface for geo-tagging entities. + * + * @package Elgg.Core + * @subpackage SocialModel.Locatable + */ +interface Locatable { + /** + * Set a location text + * + * @param string $location Textual representation of location + * + * @return bool + */ + public function setLocation($location); + + /** + * Set latitude and longitude tags for a given entity. + * + * @param float $lat Latitude + * @param float $long Longitude + * + * @return bool + */ + public function setLatLong($lat, $long); + + /** + * Get the contents of the ->geo:lat field. + * + * @return int + */ + public function getLatitude(); + + /** + * Get the contents of the ->geo:lat field. + * + * @return int + */ + public function getLongitude(); + + /** + * Get the ->location metadata. + * + * @return string + */ + public function getLocation(); +} diff --git a/engine/classes/Loggable.php b/engine/classes/Loggable.php index e12641410..b9e8bf26b 100644 --- a/engine/classes/Loggable.php +++ b/engine/classes/Loggable.php @@ -1,49 +1,65 @@ -<?php
-/**
- * Interface that provides an interface which must be implemented by all objects wishing to be
- * recorded in the system log (and by extension the river).
- *
- * This interface defines a set of methods that permit the system log functions to hook in and retrieve
- * the necessary information and to identify what events can actually be logged.
- *
- * To have events involving your object to be logged simply implement this interface.
- *
- * @author Curverider Ltd
- */
-interface Loggable {
- /**
- * Return an identification for the object for storage in the system log.
- * This id must be an integer.
- *
- * @return int
- */
- public function getSystemLogID();
-
- /**
- * Return the class name of the object.
- * Added as a function because get_class causes errors for some reason.
- */
- public function getClassName();
-
- /**
- * Return the type of the object - eg. object, group, user, relationship, metadata, annotation etc
- */
- public function getType();
-
- /**
- * Return a subtype. For metadata & annotations this is the 'name' and for relationship this is the relationship type.
- */
- public function getSubtype();
-
- /**
- * For a given ID, return the object associated with it.
- * This is used by the river functionality primarily.
- * This is useful for checking access permissions etc on objects.
- */
- public function getObjectFromID($id);
-
- /**
- * Return the GUID of the owner of this object.
- */
- public function getObjectOwnerGUID();
-}
\ No newline at end of file +<?php +/** + * Interface that provides an interface which must be implemented by all objects wishing to be + * recorded in the system log (and by extension the river). + * + * This interface defines a set of methods that permit the system log functions to + * hook in and retrieve the necessary information and to identify what events can + * actually be logged. + * + * To have events involving your object to be logged simply implement this interface. + * + * @package Elgg.Core + * @subpackage DataModel.Loggable + */ +interface Loggable { + /** + * Return an identification for the object for storage in the system log. + * This id must be an integer. + * + * @return int + */ + public function getSystemLogID(); + + /** + * Return the class name of the object. + * Added as a function because get_class causes errors for some reason. + * + * @return string + */ + public function getClassName(); + + /** + * Return the type of the object - eg. object, group, user, relationship, metadata, annotation etc + * + * @return string + */ + public function getType(); + + /** + * Return a subtype. For metadata & annotations this is the 'name' and for relationship this is the + * relationship type. + * + * @return string + */ + public function getSubtype(); + + /** + * For a given ID, return the object associated with it. + * This is used by the river functionality primarily. + * This is useful for checking access permissions etc on objects. + * + * @param int $id GUID of an entity + * + * @return ElggEntity + */ + public function getObjectFromID($id); + + /** + * Return the GUID of the owner of this object. + * + * @return int + * @deprecated 1.8 Use getOwnerGUID() instead + */ + public function getObjectOwnerGUID(); +} 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/NotImplementedException.php b/engine/classes/NotImplementedException.php new file mode 100644 index 000000000..d1decf75c --- /dev/null +++ b/engine/classes/NotImplementedException.php @@ -0,0 +1,10 @@ +<?php +/** + * NotImplementedException + * Thrown when a method or function has not been implemented, primarily used + * in development... you should not see these! + * + * @package Elgg.Core + * @subpackage Exception + */ +class NotImplementedException extends CallException {} diff --git a/engine/classes/Notable.php b/engine/classes/Notable.php index 3c133d494..0c21af27d 100644 --- a/engine/classes/Notable.php +++ b/engine/classes/Notable.php @@ -1,30 +1,41 @@ -<?php
-/**
- * Calendar interface for events.
- *
- */
-interface Notable {
- /**
- * Calendar functionality.
- * This function sets the time of an object on a calendar listing.
- *
- * @param int $hour If ommitted, now is assumed.
- * @param int $minute If ommitted, now is assumed.
- * @param int $second If ommitted, now is assumed.
- * @param int $day If ommitted, now is assumed.
- * @param int $month If ommitted, now is assumed.
- * @param int $year If ommitted, now is assumed.
- * @param int $duration Duration of event, remainder of the day is assumed.
- */
- public function setCalendarTimeAndDuration($hour = NULL, $minute = NULL, $second = NULL, $day = NULL, $month = NULL, $year = NULL, $duration = NULL);
-
- /**
- * Return the start timestamp.
- */
- public function getCalendarStartTime();
-
- /**
- * Return the end timestamp.
- */
- public function getCalendarEndTime();
-}
\ No newline at end of file +<?php +/** + * Calendar interface for events. + * + * @package Elgg.Core + * @subpackage DataModel.Notable + * + * @todo Implement or remove. + */ +interface Notable { + /** + * Calendar functionality. + * This function sets the time of an object on a calendar listing. + * + * @param int $hour If ommitted, now is assumed. + * @param int $minute If ommitted, now is assumed. + * @param int $second If ommitted, now is assumed. + * @param int $day If ommitted, now is assumed. + * @param int $month If ommitted, now is assumed. + * @param int $year If ommitted, now is assumed. + * @param int $duration Duration of event, remainder of the day is assumed. + * + * @return bool + */ + public function setCalendarTimeAndDuration($hour = NULL, $minute = NULL, $second = NULL, + $day = NULL, $month = NULL, $year = NULL, $duration = NULL); + + /** + * Return the start timestamp. + * + * @return int + */ + public function getCalendarStartTime(); + + /** + * Return the end timestamp. + * + * @return int + */ + public function getCalendarEndTime(); +} diff --git a/engine/classes/NotificationException.php b/engine/classes/NotificationException.php new file mode 100644 index 000000000..71c742f17 --- /dev/null +++ b/engine/classes/NotificationException.php @@ -0,0 +1,8 @@ +<?php +/** + * Notification exception. + * + * @package Elgg.Core + * @subpackage Exception + */ +class NotificationException extends Exception {} diff --git a/engine/classes/ODD.php b/engine/classes/ODD.php index a4118ee15..fa5b616fc 100644 --- a/engine/classes/ODD.php +++ b/engine/classes/ODD.php @@ -1,94 +1,131 @@ -<?php
-/**
- * Open Data Definition (ODD) superclass.
- * @package Elgg
- * @subpackage Core
- * @author Curverider Ltd
- */
-abstract class ODD {
- /**
- * Attributes.
- */
- private $attributes = array();
-
- /**
- * Optional body.
- */
- private $body;
-
- /**
- * Construct an ODD document with initial values.
- */
- public function __construct() {
- $this->body = "";
- }
-
- public function getAttributes() {
- return $this->attributes;
- }
-
- public function setAttribute($key, $value) {
- $this->attributes[$key] = $value;
- }
-
- public function getAttribute($key) {
- if (isset($this->attributes[$key])) {
- return $this->attributes[$key];
- }
-
- return NULL;
- }
-
- public function setBody($value) {
- $this->body = $value;
- }
-
- public function getBody() {
- return $this->body;
- }
-
- /**
- * Set the published time.
- *
- * @param int $time Unix timestamp
- */
- public function setPublished($time) {
- $this->attributes['published'] = date("r", $time);
- }
-
- /**
- * Return the published time as a unix timestamp.
- *
- * @return int or false on failure.
- */
- public function getPublishedAsTime() {
- return strtotime($this->attributes['published']);
- }
-
- /**
- * For serialisation, implement to return a string name of the tag eg "header" or "metadata".
- * @return string
- */
- abstract protected function getTagName();
-
- /**
- * Magic function to generate valid ODD XML for this item.
- */
- public function __toString() {
- // Construct attributes
- $attr = "";
- foreach ($this->attributes as $k => $v) {
- $attr .= ($v!="") ? "$k=\"$v\" " : "";
- }
-
- $body = $this->getBody();
- $tag = $this->getTagName();
-
- $end = "/>";
- if ($body!="") {
- $end = "><![CDATA[$body]]></{$tag}>";
- }
-
- return "<{$tag} $attr" . $end . "\n";
- }
-}
+<?php +/** + * Open Data Definition (ODD) superclass. + * + * @package Elgg.Core + * @subpackage ODD + */ +abstract class ODD { + /** + * Attributes. + */ + private $attributes = array(); + + /** + * Optional body. + */ + private $body; + + /** + * Construct an ODD document with initial values. + */ + public function __construct() { + $this->body = ""; + } + + /** + * Returns an array of attributes + * + * @return array + */ + public function getAttributes() { + return $this->attributes; + } + + /** + * Sets an attribute + * + * @param string $key Name + * @param mixed $value Value + * + * @return void + */ + public function setAttribute($key, $value) { + $this->attributes[$key] = $value; + } + + /** + * Returns an attribute + * + * @param string $key Name + * + * @return mixed + */ + public function getAttribute($key) { + if (isset($this->attributes[$key])) { + return $this->attributes[$key]; + } + + return NULL; + } + + /** + * Sets the body of the ODD. + * + * @param mixed $value Value + * + * @return void + */ + public function setBody($value) { + $this->body = $value; + } + + /** + * Gets the body of the ODD. + * + * @return mixed + */ + public function getBody() { + return $this->body; + } + + /** + * Set the published time. + * + * @param int $time Unix timestamp + * + * @return void + */ + public function setPublished($time) { + $this->attributes['published'] = date("r", $time); + } + + /** + * Return the published time as a unix timestamp. + * + * @return int or false on failure. + */ + public function getPublishedAsTime() { + return strtotime($this->attributes['published']); + } + + /** + * For serialisation, implement to return a string name of the tag eg "header" or "metadata". + * + * @return string + */ + abstract protected function getTagName(); + + /** + * Magic function to generate valid ODD XML for this item. + * + * @return string + */ + public function __toString() { + // Construct attributes + $attr = ""; + foreach ($this->attributes as $k => $v) { + $attr .= ($v != "") ? "$k=\"$v\" " : ""; + } + + $body = $this->getBody(); + $tag = $this->getTagName(); + + $end = "/>"; + if ($body != "") { + $end = "><![CDATA[$body]]></{$tag}>"; + } + + return "<{$tag} $attr" . $end . "\n"; + } +} diff --git a/engine/classes/ODDDocument.php b/engine/classes/ODDDocument.php index 0c8731a75..540c35a3b 100644 --- a/engine/classes/ODDDocument.php +++ b/engine/classes/ODDDocument.php @@ -1,129 +1,202 @@ -<?php
-/**
- * @class ODDDocument ODD Document container.
- * This class is used during import and export to construct.
- * @author Curverider Ltd
- */
-class ODDDocument implements Iterator {
- /**
- * ODD Version
- *
- * @var string
- */
- private $ODDSupportedVersion = "1.0";
-
- /**
- * Elements of the document.
- */
- private $elements;
-
- /**
- * Optional wrapper factory.
- */
- private $wrapperfactory;
-
- public function __construct(array $elements = NULL) {
- if ($elements) {
- if (is_array($elements)) {
- $this->elements = $elements;
- } else {
- $this->addElement($elements);
- }
- } else {
- $this->elements = array();
- }
- }
-
- /**
- * Return the version of ODD being used.
- *
- * @return string
- */
- public function getVersion() {
- return $this->ODDSupportedVersion;
- }
-
- public function getNumElements() {
- return count($this->elements);
- }
-
- public function addElement(ODD $element) {
- if (!is_array($this->elements)) {
- $this->elements = array();
- $this->elements[] = $element;
- }
- }
-
- public function addElements(array $elements) {
- foreach ($elements as $element) {
- $this->addElement($element);
- }
- }
-
- public function getElements() {
- return $this->elements;
- }
-
- /**
- * Set an optional wrapper factory to optionally embed the ODD document in another format.
- */
- public function setWrapperFactory(ODDWrapperFactory $factory) {
- $this->wrapperfactory = $factory;
- }
-
- /**
- * Magic function to generate valid ODD XML for this item.
- */
- public function __toString() {
- $xml = "";
-
- if ($this->wrapperfactory) {
- // A wrapper has been provided
- $wrapper = $this->wrapperfactory->getElementWrapper($this); // Get the wrapper for this element
-
- $xml = $wrapper->wrap($this); // Wrap this element (and subelements)
- } else {
- // Output begin tag
- $generated = date("r");
- $xml .= "<odd version=\"{$this->ODDSupportedVersion}\" generated=\"$generated\">\n";
-
- // Get XML for elements
- foreach ($this->elements as $element) {
- $xml .= "$element";
- }
-
- // Output end tag
- $xml .= "</odd>\n";
- }
-
- return $xml;
- }
-
- // ITERATOR INTERFACE //////////////////////////////////////////////////////////////
- /*
- * This lets an entity's attributes be displayed using foreach as a normal array.
- * Example: http://www.sitepoint.com/print/php5-standard-library
- */
-
- private $valid = FALSE;
-
- function rewind() {
- $this->valid = (FALSE !== reset($this->elements));
- }
-
- function current() {
- return current($this->elements);
- }
-
- function key() {
- return key($this->elements);
- }
-
- function next() {
- $this->valid = (FALSE !== next($this->elements));
- }
-
- function valid() {
- return $this->valid;
- }
-}
+<?php +/** + * This class is used during import and export to construct. + * + * @package Elgg.Core + * @subpackage ODD + */ +class ODDDocument implements Iterator { + /** + * ODD Version + * + * @var string + */ + private $ODDSupportedVersion = "1.0"; + + /** + * Elements of the document. + */ + private $elements; + + /** + * Optional wrapper factory. + */ + private $wrapperfactory; + + /** + * Create a new ODD Document. + * + * @param array $elements Elements to add + * + * @return void + */ + public function __construct(array $elements = NULL) { + if ($elements) { + if (is_array($elements)) { + $this->elements = $elements; + } else { + $this->addElement($elements); + } + } else { + $this->elements = array(); + } + } + + /** + * Return the version of ODD being used. + * + * @return string + */ + public function getVersion() { + return $this->ODDSupportedVersion; + } + + /** + * Returns the number of elements + * + * @return int + */ + public function getNumElements() { + return count($this->elements); + } + + /** + * Add an element + * + * @param ODD $element An ODD element + * + * @return void + */ + public function addElement(ODD $element) { + if (!is_array($this->elements)) { + $this->elements = array(); + } + $this->elements[] = $element; + } + + /** + * Add multiple elements at once + * + * @param array $elements Array of ODD elements + * + * @return void + */ + public function addElements(array $elements) { + foreach ($elements as $element) { + $this->addElement($element); + } + } + + /** + * Return all elements + * + * @return array + */ + public function getElements() { + return $this->elements; + } + + /** + * Set an optional wrapper factory to optionally embed the ODD document in another format. + * + * @param ODDWrapperFactory $factory The factory + * + * @return void + */ + public function setWrapperFactory(ODDWrapperFactory $factory) { + $this->wrapperfactory = $factory; + } + + /** + * Magic function to generate valid ODD XML for this item. + * + * @return string + */ + public function __toString() { + $xml = ""; + + if ($this->wrapperfactory) { + // A wrapper has been provided + $wrapper = $this->wrapperfactory->getElementWrapper($this); // Get the wrapper for this element + + $xml = $wrapper->wrap($this); // Wrap this element (and subelements) + } else { + // Output begin tag + $generated = date("r"); + $xml .= "<odd version=\"{$this->ODDSupportedVersion}\" generated=\"$generated\">\n"; + + // Get XML for elements + foreach ($this->elements as $element) { + $xml .= "$element"; + } + + // Output end tag + $xml .= "</odd>\n"; + } + + return $xml; + } + + // ITERATOR INTERFACE ////////////////////////////////////////////////////////////// + /* + * This lets an entity's attributes be displayed using foreach as a normal array. + * Example: http://www.sitepoint.com/print/php5-standard-library + */ + + private $valid = FALSE; + + /** + * Iterator interface + * + * @see Iterator::rewind() + * + * @return void + */ + function rewind() { + $this->valid = (FALSE !== reset($this->elements)); + } + + /** + * Iterator interface + * + * @see Iterator::current() + * + * @return void + */ + function current() { + return current($this->elements); + } + + /** + * Iterator interface + * + * @see Iterator::key() + * + * @return void + */ + function key() { + return key($this->elements); + } + + /** + * Iterator interface + * + * @see Iterator::next() + * + * @return void + */ + function next() { + $this->valid = (FALSE !== next($this->elements)); + } + + /** + * Iterator interface + * + * @see Iterator::valid() + * + * @return void + */ + function valid() { + return $this->valid; + } +} diff --git a/engine/classes/ODDEntity.php b/engine/classes/ODDEntity.php index 30da5f37f..e9bb5da6a 100644 --- a/engine/classes/ODDEntity.php +++ b/engine/classes/ODDEntity.php @@ -1,60 +1,34 @@ -<?php
-
-/**
- * ODD Entity class.
- * @package Elgg
- * @subpackage Core
- * @author Curverider Ltd
- */
-class ODDEntity extends ODD {
- function __construct($uuid, $class, $subclass = "") {
- parent::__construct();
-
- $this->setAttribute('uuid', $uuid);
- $this->setAttribute('class', $class);
- $this->setAttribute('subclass', $subclass);
- }
-
- protected function getTagName() { return "entity"; }
-}
-
-/**
- * ODD Metadata class.
- * @package Elgg
- * @subpackage Core
- * @author Curverider Ltd
- */
-class ODDMetaData extends ODD {
- 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);
- }
-
- protected function getTagName() {
- return "metadata";
- }
-}
-
-/**
- * ODD Relationship class.
- * @package Elgg
- * @subpackage Core
- * @author Curverider Ltd
- */
-class ODDRelationship extends ODD {
- function __construct($uuid1, $type, $uuid2) {
- parent::__construct();
-
- $this->setAttribute('uuid1', $uuid1);
- $this->setAttribute('type', $type);
- $this->setAttribute('uuid2', $uuid2);
- }
-
- protected function getTagName() { return "relationship"; }
-}
\ No newline at end of file +<?php + +/** + * ODD Entity class. + * + * @package Elgg.Core + * @subpackage ODD + */ +class ODDEntity extends ODD { + + /** + * New ODD Entity + * + * @param string $uuid A universally unique ID + * @param string $class Class + * @param string $subclass Subclass + */ + function __construct($uuid, $class, $subclass = "") { + parent::__construct(); + + $this->setAttribute('uuid', $uuid); + $this->setAttribute('class', $class); + $this->setAttribute('subclass', $subclass); + } + + /** + * Returns entity. + * + * @return 'entity' + */ + protected function getTagName() { + return "entity"; + } +} 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 new file mode 100644 index 000000000..a74303695 --- /dev/null +++ b/engine/classes/PluginException.php @@ -0,0 +1,11 @@ +<?php +/** + * PluginException + * + * A plugin Exception, thrown when an Exception occurs relating to the plugin mechanism. + * Subclass for specific plugin Exceptions. + * + * @package Elgg.Core + * @subpackage Exception + */ +class PluginException extends Exception {} diff --git a/engine/classes/RegistrationException.php b/engine/classes/RegistrationException.php new file mode 100644 index 000000000..5246efc25 --- /dev/null +++ b/engine/classes/RegistrationException.php @@ -0,0 +1,9 @@ +<?php +/** + * RegistrationException + * Could not register a new user for whatever reason. + * + * @package Elgg.Core + * @subpackage Exceptions + */ +class RegistrationException extends InstallationException {} diff --git a/engine/classes/SecurityException.php b/engine/classes/SecurityException.php new file mode 100644 index 000000000..3b6382f9e --- /dev/null +++ b/engine/classes/SecurityException.php @@ -0,0 +1,10 @@ +<?php +/** + * SecurityException + * An Security Exception, throw when a Security Exception occurs. Subclass for + * specific Security Execeptions (access problems etc) + * + * @package Elgg.Core + * @subpackage Exception + */ +class SecurityException extends Exception {} diff --git a/engine/classes/SuccessResult.php b/engine/classes/SuccessResult.php index db5769d58..ab5468ad8 100644 --- a/engine/classes/SuccessResult.php +++ b/engine/classes/SuccessResult.php @@ -1,22 +1,34 @@ -<?php
-/**
- * SuccessResult
- * Generic success result class, extend if you want to do something special.
- *
- * @author Curverider Ltd <info@elgg.com>
- * @package Elgg
- * @subpackage Core
- */
-class SuccessResult extends GenericResult {
- public static $RESULT_SUCCESS = 0; // Do not change this from 0
-
- public function SuccessResult($result) {
- $this->setResult($result);
- $this->setStatusCode(SuccessResult::$RESULT_SUCCESS);
- }
-
- public static function getInstance($result) {
- // Return a new error object.
- return new SuccessResult($result);
- }
-}
\ No newline at end of file +<?php +/** + * SuccessResult + * Generic success result class, extend if you want to do something special. + * + * @package Elgg.Core + * @subpackage WebServicesAPI + */ +class SuccessResult extends GenericResult { + // Do not change this from 0 + public static $RESULT_SUCCESS = 0; + + /** + * A new success result + * + * @param string $result The result + */ + public function __construct($result) { + $this->setResult($result); + $this->setStatusCode(SuccessResult::$RESULT_SUCCESS); + } + + /** + * Returns a new instance of this class + * + * @param unknown $result A result of some kind? + * + * @return SuccessResult + */ + public static function getInstance($result) { + // Return a new error object. + return new SuccessResult($result); + } +} diff --git a/engine/classes/XMLRPCArrayParameter.php b/engine/classes/XMLRPCArrayParameter.php index 600f5d9a7..a8edccba7 100644 --- a/engine/classes/XMLRPCArrayParameter.php +++ b/engine/classes/XMLRPCArrayParameter.php @@ -1,48 +1,56 @@ -<?php
-
-/**
- * @class XMLRPCArrayParameter An array containing other XMLRPCParameter objects.
- * @author Curverider Ltd
- */
-class XMLRPCArrayParameter extends XMLRPCParameter
-{
- /**
- * Construct an array.
- *
- * @param array $parameters Optional array of parameters, if not provided then addField must be used.
- */
- function __construct($parameters = NULL)
- {
- parent::__construct();
-
- if (is_array($parameters))
- {
- foreach ($parameters as $v)
- $this->addField($v);
- }
- }
-
- /**
- * Add a field to the container.
- *
- * @param XMLRPCParameter $value The value.
- */
- public function addField(XMLRPCParameter $value)
- {
- if (!is_array($this->value))
- $this->value = array();
-
- $this->value[] = $value;
- }
-
- function __toString()
- {
- $params = "";
- foreach ($this->value as $value)
- {
- $params .= "$value";
- }
-
- return "<array><data>$params</data></array>";
- }
-}
\ No newline at end of file +<?php + +/** + * An array containing other XMLRPCParameter objects. + * + * @package Elgg.Core + * @subpackage XMLRPC + * + */ +class XMLRPCArrayParameter extends XMLRPCParameter +{ + /** + * Construct an array. + * + * @param array $parameters Optional array of parameters, if not provided + * then addField must be used. + */ + function __construct($parameters = NULL) { + parent::__construct(); + + if (is_array($parameters)) { + foreach ($parameters as $v) { + $this->addField($v); + } + } + } + + /** + * Add a field to the container. + * + * @param XMLRPCParameter $value The value. + * + * @return void + */ + public function addField(XMLRPCParameter $value) { + if (!is_array($this->value)) { + $this->value = array(); + } + + $this->value[] = $value; + } + + /** + * Converts XML array to string + * + * @return string + */ + function __toString() { + $params = ""; + foreach ($this->value as $value) { + $params .= "$value"; + } + + return "<array><data>$params</data></array>"; + } +} diff --git a/engine/classes/XMLRPCBase64Parameter.php b/engine/classes/XMLRPCBase64Parameter.php index b32e7f3da..7db0a761c 100644 --- a/engine/classes/XMLRPCBase64Parameter.php +++ b/engine/classes/XMLRPCBase64Parameter.php @@ -1,24 +1,28 @@ -<?php
-/**
- * @class XMLRPCBase64Parameter A base 64 encoded blob of binary.
- * @author Curverider Ltd
- */
-class XMLRPCBase64Parameter extends XMLRPCParameter
-{
- /**
- * Construct a base64 encoded block
- *
- * @param string $blob Unencoded binary blob
- */
- function __construct($blob)
- {
- parent::__construct();
-
- $this->value = base64_encode($blob);
- }
-
- function __toString()
- {
- return "<value><base64>{$value}</base64></value>";
- }
-}
\ No newline at end of file +<?php +/** + * A base 64 encoded blob of binary. + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +class XMLRPCBase64Parameter extends XMLRPCParameter { + /** + * Construct a base64 encoded block + * + * @param string $blob Unencoded binary blob + */ + function __construct($blob) { + parent::__construct(); + + $this->value = base64_encode($blob); + } + + /** + * Convert to string + * + * @return string + */ + function __toString() { + return "<value><base64>{$value}</base64></value>"; + } +} diff --git a/engine/classes/XMLRPCBoolParameter.php b/engine/classes/XMLRPCBoolParameter.php index c2714ceff..607841cb8 100644 --- a/engine/classes/XMLRPCBoolParameter.php +++ b/engine/classes/XMLRPCBoolParameter.php @@ -1,20 +1,30 @@ -<?php
-/**
- * @class XMLRPCBoolParameter A boolean.
- * @author Curverider Ltd
- */
-class XMLRPCBoolParameter extends XMLRPCParameter
-{
- function __construct($value)
- {
- parent::__construct();
-
- $this->value = (bool)$value;
- }
-
- function __toString()
- {
- $code = ($this->value) ? "1" : "0";
- return "<value><boolean>{$code}</boolean></value>";
- }
-}
\ No newline at end of file +<?php +/** + * A boolean. + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +class XMLRPCBoolParameter extends XMLRPCParameter { + + /** + * New bool parameter + * + * @param bool $value Value + */ + function __construct($value) { + parent::__construct(); + + $this->value = (bool)$value; + } + + /** + * Convert to string + * + * @return string + */ + function __toString() { + $code = ($this->value) ? "1" : "0"; + return "<value><boolean>{$code}</boolean></value>"; + } +} diff --git a/engine/classes/XMLRPCCall.php b/engine/classes/XMLRPCCall.php index 09e8e6d6d..fd28f1e3e 100644 --- a/engine/classes/XMLRPCCall.php +++ b/engine/classes/XMLRPCCall.php @@ -1,60 +1,62 @@ -<?php
-/**
- * @class XMLRPCCall
- * This class represents
- * @author Curverider Ltd
- */
-class XMLRPCCall
-{
- /** Method name */
- private $methodname;
- /** Parameters */
- private $params;
-
- /**
- * Construct a new XML RPC Call
- *
- * @param string $xml
- */
- function __construct($xml)
- {
- $this->parse($xml);
- }
-
- /**
- * Return the method name associated with the call.
- *
- * @return string
- */
- public function getMethodName() { return $this->methodname; }
-
- /**
- * Return the parameters.
- * Returns a nested array of XmlElement.
- *
- * @see XmlElement
- * @return array
- */
- public function getParameters() { return $this->params; }
-
- /**
- * Parse the xml into its components according to spec.
- * This first version is a little primitive.
- *
- * @param string $xml
- */
- private function parse($xml)
- {
- $xml = xml_to_object($xml);
-
- // sanity check
- if ((isset($xml->name)) && (strcasecmp($xml->name, "methodCall")!=0))
- throw new CallException(elgg_echo('CallException:NotRPCCall'));
-
- // method name
- $this->methodname = $xml->children[0]->content;
-
- // parameters
- $this->params = $xml->children[1]->children;
- }
-}
\ No newline at end of file +<?php +/** + * An XMLRPC call + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +class XMLRPCCall { + /** Method name */ + private $methodname; + + /** Parameters */ + private $params; + + /** + * Construct a new XML RPC Call + * + * @param string $xml XML + */ + function __construct($xml) { + $this->parse($xml); + } + + /** + * Return the method name associated with the call. + * + * @return string + */ + public function getMethodName() { return $this->methodname; } + + /** + * Return the parameters. + * Returns a nested array of XmlElement. + * + * @see XmlElement + * @return array + */ + public function getParameters() { return $this->params; } + + /** + * Parse the xml into its components according to spec. + * This first version is a little primitive. + * + * @param string $xml XML + * + * @return void + */ + private function parse($xml) { + $xml = xml_to_object($xml); + + // sanity check + if ((isset($xml->name)) && (strcasecmp($xml->name, "methodCall") != 0)) { + throw new CallException(elgg_echo('CallException:NotRPCCall')); + } + + // method name + $this->methodname = $xml->children[0]->content; + + // parameters + $this->params = $xml->children[1]->children; + } +} diff --git a/engine/classes/XMLRPCDateParameter.php b/engine/classes/XMLRPCDateParameter.php index 47ba88c0f..93bbbd8f5 100644 --- a/engine/classes/XMLRPCDateParameter.php +++ b/engine/classes/XMLRPCDateParameter.php @@ -1,27 +1,33 @@ -<?php
-/**
- * @class XMLRPCDateParameter An ISO8601 data and time.
- * @author Curverider Ltd
- */
-class XMLRPCDateParameter extends XMLRPCParameter
-{
- /**
- * Construct a date
- *
- * @param int $timestamp The unix timestamp, or blank for "now".
- */
- function __construct($timestamp = 0)
- {
- parent::__construct();
-
- $this->value = $timestamp;
- if (!$timestamp)
- $this->value = time();
- }
-
- function __toString()
- {
- $value = date('c', $this->value);
- return "<value><dateTime.iso8601>{$value}</dateTime.iso8601></value>";
- }
-}
\ No newline at end of file +<?php +/** + * An ISO8601 data and time. + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +class XMLRPCDateParameter extends XMLRPCParameter { + /** + * Construct a date + * + * @param int $timestamp The unix timestamp, or blank for "now". + */ + function __construct($timestamp = 0) { + parent::__construct(); + + $this->value = $timestamp; + + if (!$timestamp) { + $this->value = time(); + } + } + + /** + * Convert to string + * + * @return string + */ + function __toString() { + $value = date('c', $this->value); + return "<value><dateTime.iso8601>{$value}</dateTime.iso8601></value>"; + } +} diff --git a/engine/classes/XMLRPCDoubleParameter.php b/engine/classes/XMLRPCDoubleParameter.php index 64cbdff91..b7834650e 100644 --- a/engine/classes/XMLRPCDoubleParameter.php +++ b/engine/classes/XMLRPCDoubleParameter.php @@ -1,19 +1,29 @@ -<?php
-/**
- * @class XMLRPCDoubleParameter A double precision signed floating point number.
- * @author Curverider Ltd
- */
-class XMLRPCDoubleParameter extends XMLRPCParameter
-{
- function __construct($value)
- {
- parent::__construct();
-
- $this->value = (float)$value;
- }
-
- function __toString()
- {
- return "<value><double>{$this->value}</double></value>";
- }
-}
+<?php +/** + * A double precision signed floating point number. + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +class XMLRPCDoubleParameter extends XMLRPCParameter { + + /** + * New XML Double + * + * @param int $value Value + */ + function __construct($value) { + parent::__construct(); + + $this->value = (float)$value; + } + + /** + * Convert to string + * + * @return string + */ + function __toString() { + return "<value><double>{$this->value}</double></value>"; + } +} diff --git a/engine/classes/XMLRPCErrorResponse.php b/engine/classes/XMLRPCErrorResponse.php index 4dfcfafea..425c075cc 100644 --- a/engine/classes/XMLRPCErrorResponse.php +++ b/engine/classes/XMLRPCErrorResponse.php @@ -1,34 +1,36 @@ -<?php
-
-/**
- * @class XMLRPCErrorResponse
- * @author Curverider Ltd
- */
-class XMLRPCErrorResponse extends XMLRPCResponse
-{
- /**
- * Set the error response and error code.
- *
- * @param string $message The message
- * @param int $code Error code (default = system error as defined by http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php)
- */
- function __construct($message, $code = -32400)
- {
- $this->addParameter(
- new XMLRPCStructParameter(
- array (
- 'faultCode' => new XMLRPCIntParameter($code),
- 'faultString' => new XMLRPCStringParameter($message)
- )
- )
- );
- }
-
- /**
- * Output to XML.
- */
- public function __toString()
- {
- return "<methodResponse><fault><value>{$this->parameters[0]}</value></fault></methodResponse>";
- }
-}
\ No newline at end of file +<?php + +/** + * XMLRPC Error Response + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +class XMLRPCErrorResponse extends XMLRPCResponse { + /** + * Set the error response and error code. + * + * @param string $message The message + * @param int $code Error code (default = system error as defined by + * http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php) + */ + function __construct($message, $code = -32400) { + $this->addParameter( + new XMLRPCStructParameter( + array ( + 'faultCode' => new XMLRPCIntParameter($code), + 'faultString' => new XMLRPCStringParameter($message) + ) + ) + ); + } + + /** + * Output to XML. + * + * @return string + */ + public function __toString() { + return "<methodResponse><fault><value>{$this->parameters[0]}</value></fault></methodResponse>"; + } +} diff --git a/engine/classes/XMLRPCIntParameter.php b/engine/classes/XMLRPCIntParameter.php index 2305a66a7..0fc146165 100644 --- a/engine/classes/XMLRPCIntParameter.php +++ b/engine/classes/XMLRPCIntParameter.php @@ -1,19 +1,29 @@ -<?php
-/**
- * @class XMLRPCIntParameter An Integer.
- * @author Curverider Ltd
- */
-class XMLRPCIntParameter extends XMLRPCParameter
-{
- function __construct($value)
- {
- parent::__construct();
-
- $this->value = (int)$value;
- }
-
- function __toString()
- {
- return "<value><i4>{$this->value}</i4></value>";
- }
-}
+<?php +/** + * An Integer. + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +class XMLRPCIntParameter extends XMLRPCParameter { + + /** + * A new XML int + * + * @param int $value Value + */ + function __construct($value) { + parent::__construct(); + + $this->value = (int)$value; + } + + /** + * Convert to string + * + * @return string + */ + function __toString() { + return "<value><i4>{$this->value}</i4></value>"; + } +} diff --git a/engine/classes/XMLRPCParameter.php b/engine/classes/XMLRPCParameter.php index f9e04a073..ffbad8082 100644 --- a/engine/classes/XMLRPCParameter.php +++ b/engine/classes/XMLRPCParameter.php @@ -1,12 +1,16 @@ -<?php
-/**
- * @class XMLRPCParameter Superclass for all RPC parameters.
- * @author Curverider Ltd
- */
-abstract class XMLRPCParameter
-{
- protected $value;
-
- function __construct() { }
-
-}
\ No newline at end of file +<?php +/** + * Superclass for all RPC parameters. + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +abstract class XMLRPCParameter { + protected $value; + + /** + * Set initial values + */ + function __construct() { } + +} diff --git a/engine/classes/XMLRPCResponse.php b/engine/classes/XMLRPCResponse.php index 1dea8d682..a6256d385 100644 --- a/engine/classes/XMLRPCResponse.php +++ b/engine/classes/XMLRPCResponse.php @@ -1,29 +1,71 @@ -<?php
-
-/**
- * @class XMLRPCResponse XML-RPC Response.
- * @author Curverider Ltd
- */
-abstract class XMLRPCResponse
-{
- /** An array of parameters */
- protected $parameters = array();
-
- /**
- * Add a parameter here.
- *
- * @param XMLRPCParameter $param The parameter.
- */
- public function addParameter(XMLRPCParameter $param)
- {
- if (!is_array($this->parameters))
- $this->parameters = array();
-
- $this->parameters[] = $param;
- }
-
- public function addInt($value) { $this->addParameter(new XMLRPCIntParameter($value)); }
- public function addString($value) { $this->addParameter(new XMLRPCStringParameter($value)); }
- public function addDouble($value) { $this->addParameter(new XMLRPCDoubleParameter($value)); }
- public function addBoolean($value) { $this->addParameter(new XMLRPCBoolParameter($value)); }
-}
\ No newline at end of file +<?php + +/** + * XML-RPC Response. + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +abstract class XMLRPCResponse { + /** An array of parameters */ + protected $parameters = array(); + + /** + * Add a parameter here. + * + * @param XMLRPCParameter $param The parameter. + * + * @return void + */ + public function addParameter(XMLRPCParameter $param) { + if (!is_array($this->parameters)) { + $this->parameters = array(); + } + + $this->parameters[] = $param; + } + + /** + * Add an integer + * + * @param int $value Value + * + * @return void + */ + public function addInt($value) { + $this->addParameter(new XMLRPCIntParameter($value)); + } + + /** + * Add a string + * + * @param string $value Value + * + * @return void + */ + public function addString($value) { + $this->addParameter(new XMLRPCStringParameter($value)); + } + + /** + * Add a double + * + * @param int $value Value + * + * @return void + */ + public function addDouble($value) { + $this->addParameter(new XMLRPCDoubleParameter($value)); + } + + /** + * Add a boolean + * + * @param bool $value Value + * + * @return void + */ + public function addBoolean($value) { + $this->addParameter(new XMLRPCBoolParameter($value)); + } +} diff --git a/engine/classes/XMLRPCStringParameter.php b/engine/classes/XMLRPCStringParameter.php index bf9097747..35b28214b 100644 --- a/engine/classes/XMLRPCStringParameter.php +++ b/engine/classes/XMLRPCStringParameter.php @@ -1,20 +1,30 @@ -<?php
-/**
- * @class XMLRPCStringParameter A string.
- * @author Curverider Ltd
- */
-class XMLRPCStringParameter extends XMLRPCParameter
-{
- function __construct($value)
- {
- parent::__construct();
-
- $this->value = $value;
- }
-
- function __toString()
- {
- $value = htmlentities($this->value);
- return "<value><string>{$value}</string></value>";
- }
-}
+<?php +/** + * A string. + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +class XMLRPCStringParameter extends XMLRPCParameter { + + /** + * A new XML string + * + * @param string $value Value + */ + function __construct($value) { + parent::__construct(); + + $this->value = $value; + } + + /** + * Convert to XML string + * + * @return string + */ + function __toString() { + $value = htmlentities($this->value); + return "<value><string>{$value}</string></value>"; + } +} diff --git a/engine/classes/XMLRPCStructParameter.php b/engine/classes/XMLRPCStructParameter.php index 326a82804..694ddf5df 100644 --- a/engine/classes/XMLRPCStructParameter.php +++ b/engine/classes/XMLRPCStructParameter.php @@ -1,49 +1,55 @@ -<?php
-
-/**
- * @class XMLRPCStructParameter A structure containing other XMLRPCParameter objects.
- * @author Curverider Ltd
- */
-class XMLRPCStructParameter extends XMLRPCParameter
-{
- /**
- * Construct a struct.
- *
- * @param array $parameters Optional associated array of parameters, if not provided then addField must be used.
- */
- function __construct($parameters = NULL)
- {
- parent::__construct();
-
- if (is_array($parameters))
- {
- foreach ($parameters as $k => $v)
- $this->addField($k, $v);
- }
- }
-
- /**
- * Add a field to the container.
- *
- * @param string $name The name of the field.
- * @param XMLRPCParameter $value The value.
- */
- public function addField($name, XMLRPCParameter $value)
- {
- if (!is_array($this->value))
- $this->value = array();
-
- $this->value[$name] = $value;
- }
-
- function __toString()
- {
- $params = "";
- foreach ($this->value as $k => $v)
- {
- $params .= "<member><name>$k</name>$v</member>";
- }
-
- return "<value><struct>$params</struct></value>";
- }
-}
\ No newline at end of file +<?php + +/** + * A structure containing other XMLRPCParameter objects. + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +class XMLRPCStructParameter extends XMLRPCParameter { + /** + * Construct a struct. + * + * @param array $parameters Optional associated array of parameters, if + * not provided then addField must be used. + */ + function __construct($parameters = NULL) { + parent::__construct(); + + if (is_array($parameters)) { + foreach ($parameters as $k => $v) { + $this->addField($k, $v); + } + } + } + + /** + * Add a field to the container. + * + * @param string $name The name of the field. + * @param XMLRPCParameter $value The value. + * + * @return void + */ + public function addField($name, XMLRPCParameter $value) { + if (!is_array($this->value)) { + $this->value = array(); + } + + $this->value[$name] = $value; + } + + /** + * Convert to string + * + * @return string + */ + function __toString() { + $params = ""; + foreach ($this->value as $k => $v) { + $params .= "<member><name>$k</name>$v</member>"; + } + + return "<value><struct>$params</struct></value>"; + } +} diff --git a/engine/classes/XMLRPCSuccessResponse.php b/engine/classes/XMLRPCSuccessResponse.php index cffa64439..e02e82c5c 100644 --- a/engine/classes/XMLRPCSuccessResponse.php +++ b/engine/classes/XMLRPCSuccessResponse.php @@ -1,19 +1,22 @@ -<?php
-/**
- * @class XMLRPCSuccessResponse
- * @author Curverider Ltd
- */
-class XMLRPCSuccessResponse extends XMLRPCResponse
-{
- /**
- * Output to XML.
- */
- public function __toString()
- {
- $params = "";
- foreach ($this->parameters as $param)
- $params .= "<param>$param</param>\n";
-
- return "<methodResponse><params>$params</params></methodResponse>";
- }
-}
\ No newline at end of file +<?php +/** + * Success Response + * + * @package Elgg.Core + * @subpackage XMLRPC + */ +class XMLRPCSuccessResponse extends XMLRPCResponse { + /** + * Output to XML. + * + * @return string + */ + public function __toString() { + $params = ""; + foreach ($this->parameters as $param) { + $params .= "<param>$param</param>\n"; + } + + return "<methodResponse><params>$params</params></methodResponse>"; + } +} diff --git a/engine/classes/XmlElement.php b/engine/classes/XmlElement.php index 17e3151a8..280bba664 100644 --- a/engine/classes/XmlElement.php +++ b/engine/classes/XmlElement.php @@ -1,19 +1,20 @@ -<?php
-/**
- * @class XmlElement
- * A class representing an XML element for import.
- */
-class XmlElement
-{
- /** The name of the element */
- public $name;
-
- /** The attributes */
- public $attributes;
-
- /** CData */
- public $content;
-
- /** Child elements */
- public $children;
-};
\ No newline at end of file +<?php +/** + * A class representing an XML element for import. + * + * @package Elgg.Core + * @subpackage XML + */ +class XmlElement { + /** The name of the element */ + public $name; + + /** The attributes */ + public $attributes; + + /** CData */ + public $content; + + /** Child elements */ + public $children; +}; |
