aboutsummaryrefslogtreecommitdiff
path: root/engine/classes
diff options
context:
space:
mode:
Diffstat (limited to 'engine/classes')
-rw-r--r--engine/classes/APIException.php2
-rw-r--r--engine/classes/CallException.php2
-rw-r--r--engine/classes/ClassNotFoundException.php2
-rw-r--r--engine/classes/CronException.php2
-rw-r--r--engine/classes/ElggAccess.php6
-rw-r--r--engine/classes/ElggAnnotation.php46
-rw-r--r--engine/classes/ElggAttributeLoader.php248
-rw-r--r--engine/classes/ElggAutoP.php336
-rw-r--r--engine/classes/ElggBatch.php433
-rw-r--r--engine/classes/ElggCache.php45
-rw-r--r--engine/classes/ElggCrypto.php208
-rw-r--r--engine/classes/ElggData.php24
-rw-r--r--engine/classes/ElggDiskFilestore.php52
-rw-r--r--engine/classes/ElggEntity.php699
-rw-r--r--engine/classes/ElggExtender.php26
-rw-r--r--engine/classes/ElggFile.php111
-rw-r--r--engine/classes/ElggFileCache.php27
-rw-r--r--engine/classes/ElggFilestore.php2
-rw-r--r--engine/classes/ElggGroup.php96
-rw-r--r--engine/classes/ElggGroupItemVisibility.php93
-rw-r--r--engine/classes/ElggHMACCache.php2
-rw-r--r--engine/classes/ElggLRUCache.php181
-rw-r--r--engine/classes/ElggMemcache.php49
-rw-r--r--engine/classes/ElggMenuBuilder.php291
-rw-r--r--engine/classes/ElggMenuItem.php590
-rw-r--r--engine/classes/ElggMetadata.php85
-rw-r--r--engine/classes/ElggObject.php101
-rw-r--r--engine/classes/ElggPAM.php15
-rw-r--r--engine/classes/ElggPlugin.php946
-rw-r--r--engine/classes/ElggPluginManifest.php290
-rw-r--r--engine/classes/ElggPluginManifestParser.php8
-rw-r--r--engine/classes/ElggPluginManifestParser17.php15
-rw-r--r--engine/classes/ElggPluginManifestParser18.php29
-rw-r--r--engine/classes/ElggPluginPackage.php599
-rw-r--r--engine/classes/ElggPriorityList.php366
-rw-r--r--engine/classes/ElggRelationship.php30
-rw-r--r--engine/classes/ElggRiverItem.php115
-rw-r--r--engine/classes/ElggSession.php16
-rw-r--r--engine/classes/ElggSharedMemoryCache.php2
-rw-r--r--engine/classes/ElggSite.php132
-rw-r--r--engine/classes/ElggStaticVariableCache.php10
-rw-r--r--engine/classes/ElggTranslit.php269
-rw-r--r--engine/classes/ElggUser.php237
-rw-r--r--engine/classes/ElggVolatileMetadataCache.php355
-rw-r--r--engine/classes/ElggWidget.php79
-rw-r--r--engine/classes/ElggXMLElement.php131
-rw-r--r--engine/classes/ErrorResult.php4
-rw-r--r--engine/classes/ExportException.php2
-rw-r--r--engine/classes/Exportable.php2
-rw-r--r--engine/classes/Friendable.php2
-rw-r--r--engine/classes/GenericResult.php2
-rw-r--r--engine/classes/ImportException.php2
-rw-r--r--engine/classes/IncompleteEntityException.php10
-rw-r--r--engine/classes/Locatable.php4
-rw-r--r--engine/classes/Loggable.php2
-rw-r--r--engine/classes/LoginException.php2
-rw-r--r--engine/classes/Notable.php2
-rw-r--r--engine/classes/ODDDocument.php2
-rw-r--r--engine/classes/ODDEntity.php72
-rw-r--r--engine/classes/ODDMetaData.php39
-rw-r--r--engine/classes/ODDRelationship.php33
-rw-r--r--engine/classes/PluginException.php2
-rw-r--r--engine/classes/RegistrationException.php2
-rw-r--r--engine/classes/SuccessResult.php4
-rw-r--r--engine/classes/XMLRPCArrayParameter.php2
-rw-r--r--engine/classes/XMLRPCBase64Parameter.php2
-rw-r--r--engine/classes/XMLRPCBoolParameter.php2
-rw-r--r--engine/classes/XMLRPCCall.php6
-rw-r--r--engine/classes/XMLRPCDateParameter.php2
-rw-r--r--engine/classes/XMLRPCErrorResponse.php2
-rw-r--r--engine/classes/XMLRPCParameter.php2
-rw-r--r--engine/classes/XMLRPCResponse.php2
-rw-r--r--engine/classes/XMLRPCStructParameter.php2
-rw-r--r--engine/classes/XMLRPCSuccessResponse.php2
-rw-r--r--engine/classes/XmlElement.php2
75 files changed, 6526 insertions, 1091 deletions
diff --git a/engine/classes/APIException.php b/engine/classes/APIException.php
index 377538c36..b6e1c347b 100644
--- a/engine/classes/APIException.php
+++ b/engine/classes/APIException.php
@@ -8,4 +8,4 @@
* @package Elgg.Core
* @subpackage Exceptions.Stub
*/
-class APIException extends Exception {} \ No newline at end of file
+class APIException extends Exception {}
diff --git a/engine/classes/CallException.php b/engine/classes/CallException.php
index 02c580a52..22b8f14f5 100644
--- a/engine/classes/CallException.php
+++ b/engine/classes/CallException.php
@@ -7,4 +7,4 @@
* @package Elgg.Core
* @subpackage Exceptions.Stub
*/
-class CallException extends Exception {} \ No newline at end of file
+class CallException extends Exception {}
diff --git a/engine/classes/ClassNotFoundException.php b/engine/classes/ClassNotFoundException.php
index 5170b5333..6a9bcd327 100644
--- a/engine/classes/ClassNotFoundException.php
+++ b/engine/classes/ClassNotFoundException.php
@@ -7,4 +7,4 @@
* @package Elgg.Core
* @subpackage Exceptions
*/
-class ClassNotFoundException extends ClassException {} \ No newline at end of file
+class ClassNotFoundException extends ClassException {}
diff --git a/engine/classes/CronException.php b/engine/classes/CronException.php
index 3604c21b9..86370ef31 100644
--- a/engine/classes/CronException.php
+++ b/engine/classes/CronException.php
@@ -7,4 +7,4 @@
* @package Elgg
* @subpackage Exceptions.Stub
*/
-class CronException extends Exception {} \ No newline at end of file
+class CronException extends Exception {}
diff --git a/engine/classes/ElggAccess.php b/engine/classes/ElggAccess.php
index 68607962b..0aed477fc 100644
--- a/engine/classes/ElggAccess.php
+++ b/engine/classes/ElggAccess.php
@@ -16,6 +16,7 @@ class ElggAccess {
*/
private $ignore_access;
+ // @codingStandardsIgnoreStart
/**
* Get current ignore access setting.
*
@@ -26,6 +27,7 @@ class ElggAccess {
elgg_deprecated_notice('ElggAccess::get_ignore_access() is deprecated by ElggAccess::getIgnoreAccess()', 1.8);
return $this->getIgnoreAccess();
}
+ // @codingStandardsIgnoreEnd
/**
* Get current ignore access setting.
@@ -36,6 +38,7 @@ class ElggAccess {
return $this->ignore_access;
}
+ // @codingStandardsIgnoreStart
/**
* Set ignore access.
*
@@ -49,6 +52,7 @@ class ElggAccess {
elgg_deprecated_notice('ElggAccess::set_ignore_access() is deprecated by ElggAccess::setIgnoreAccess()', 1.8);
return $this->setIgnoreAccess($ignore);
}
+ // @codingStandardsIgnoreEnd
/**
* Set ignore access.
@@ -63,4 +67,4 @@ class ElggAccess {
return $prev;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggAnnotation.php b/engine/classes/ElggAnnotation.php
index 913d909c5..175e7049d 100644
--- a/engine/classes/ElggAnnotation.php
+++ b/engine/classes/ElggAnnotation.php
@@ -11,6 +11,9 @@
* @package Elgg.Core
* @subpackage DataModel.Annotations
* @link http://docs.elgg.org/DataModel/Annotations
+ *
+ * @property string $value_type
+ * @property string $enabled
*/
class ElggAnnotation extends ElggExtender {
@@ -28,26 +31,26 @@ class ElggAnnotation extends ElggExtender {
}
/**
- * Construct a new annotation, optionally from a given id value or db object.
+ * Construct a new annotation object
*
- * @param mixed $id The annotation ID
+ * @param mixed $id The annotation ID or a database row as stdClass object
*/
function __construct($id = null) {
$this->initializeAttributes();
if (!empty($id)) {
+ // Create from db row
if ($id instanceof stdClass) {
$annotation = $id;
- } else {
- $annotation = get_annotation($id);
- }
- if ($annotation) {
$objarray = (array) $annotation;
-
foreach ($objarray as $key => $value) {
$this->attributes[$key] = $value;
}
+ } else {
+ // get an ElggAnnotation object and copy its attributes
+ $annotation = elgg_get_annotation_from_id($id);
+ $this->attributes = $annotation->attributes;
}
}
}
@@ -56,6 +59,8 @@ class ElggAnnotation extends ElggExtender {
* Save this instance
*
* @return int an object id
+ *
+ * @throws IOException
*/
function save() {
if ($this->id > 0) {
@@ -78,7 +83,28 @@ class ElggAnnotation extends ElggExtender {
* @return bool
*/
function delete() {
- return delete_annotation($this->id);
+ elgg_delete_river(array('annotation_id' => $this->id));
+ return elgg_delete_metastring_based_object_by_id($this->id, 'annotations');
+ }
+
+ /**
+ * Disable the annotation.
+ *
+ * @return bool
+ * @since 1.8
+ */
+ function disable() {
+ return elgg_set_metastring_based_object_enabled_by_id($this->id, 'no', 'annotations');
+ }
+
+ /**
+ * Enable the annotation.
+ *
+ * @return bool
+ * @since 1.8
+ */
+ function enable() {
+ return elgg_set_metastring_based_object_enabled_by_id($this->id, 'yes', 'annotations');
}
/**
@@ -102,6 +128,6 @@ class ElggAnnotation extends ElggExtender {
* @return ElggAnnotation
*/
public function getObjectFromID($id) {
- return get_annotation($id);
+ return elgg_get_annotation_from_id($id);
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggAttributeLoader.php b/engine/classes/ElggAttributeLoader.php
new file mode 100644
index 000000000..ffc80b02d
--- /dev/null
+++ b/engine/classes/ElggAttributeLoader.php
@@ -0,0 +1,248 @@
+<?php
+
+/**
+ * Loads ElggEntity attributes from DB or validates those passed in via constructor
+ *
+ * @access private
+ *
+ * @package Elgg.Core
+ * @subpackage DataModel
+ */
+class ElggAttributeLoader {
+
+ /**
+ * @var array names of attributes in all entities
+ */
+ protected static $primary_attr_names = array(
+ 'guid',
+ 'type',
+ 'subtype',
+ 'owner_guid',
+ 'container_guid',
+ 'site_guid',
+ 'access_id',
+ 'time_created',
+ 'time_updated',
+ 'last_action',
+ 'enabled',
+ );
+
+ /**
+ * @var array names of secondary attributes required for the entity
+ */
+ protected $secondary_attr_names = array();
+
+ /**
+ * @var string entity type (not class) required for fetched primaries
+ */
+ protected $required_type;
+
+ /**
+ * @var array
+ */
+ protected $initialized_attributes;
+
+ /**
+ * @var string class of object being loaded
+ */
+ protected $class;
+
+ /**
+ * @var bool should access control be considered when fetching entity?
+ */
+ public $requires_access_control = true;
+
+ /**
+ * @var callable function used to load attributes from {prefix}entities table
+ */
+ public $primary_loader = 'get_entity_as_row';
+
+ /**
+ * @var callable function used to load attributes from secondary table
+ */
+ public $secondary_loader = '';
+
+ /**
+ * @var callable function used to load all necessary attributes
+ */
+ public $full_loader = '';
+
+ /**
+ * Constructor
+ *
+ * @param string $class class of object being loaded
+ * @param string $required_type entity type this is being used to populate
+ * @param array $initialized_attrs attributes after initializeAttributes() has been run
+ * @throws InvalidArgumentException
+ */
+ public function __construct($class, $required_type, array $initialized_attrs) {
+ if (!is_string($class)) {
+ throw new InvalidArgumentException('$class must be a class name.');
+ }
+ $this->class = $class;
+
+ if (!is_string($required_type)) {
+ throw new InvalidArgumentException('$requiredType must be a system entity type.');
+ }
+ $this->required_type = $required_type;
+
+ $this->initialized_attributes = $initialized_attrs;
+ unset($initialized_attrs['tables_split'], $initialized_attrs['tables_loaded']);
+ $all_attr_names = array_keys($initialized_attrs);
+ $this->secondary_attr_names = array_diff($all_attr_names, self::$primary_attr_names);
+ }
+
+ /**
+ * Get primary attributes missing that are missing
+ *
+ * @param stdClass $row Database row
+ * @return array
+ */
+ protected function isMissingPrimaries($row) {
+ return array_diff(self::$primary_attr_names, array_keys($row)) !== array();
+ }
+
+ /**
+ * Get secondary attributes that are missing
+ *
+ * @param stdClass $row Database row
+ * @return array
+ */
+ protected function isMissingSecondaries($row) {
+ return array_diff($this->secondary_attr_names, array_keys($row)) !== array();
+ }
+
+ /**
+ * Check that the type is correct
+ *
+ * @param stdClass $row Database row
+ * @return void
+ * @throws InvalidClassException
+ */
+ protected function checkType($row) {
+ if ($row['type'] !== $this->required_type) {
+ $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($row['guid'], $this->class));
+ throw new InvalidClassException($msg);
+ }
+ }
+
+ /**
+ * Get all required attributes for the entity, validating any that are passed in. Returns empty array
+ * if can't be loaded (Check $failure_reason).
+ *
+ * This function splits loading between "primary" attributes (those in {prefix}entities table) and
+ * "secondary" attributes (e.g. those in {prefix}objects_entity), but can load all at once if a
+ * combined loader is available.
+ *
+ * @param mixed $row a row loaded from DB (array or stdClass) or a GUID
+ * @return array will be empty if failed to load all attributes (access control or entity doesn't exist)
+ *
+ * @throws InvalidArgumentException|LogicException|IncompleteEntityException
+ */
+ public function getRequiredAttributes($row) {
+ if (!is_array($row) && !($row instanceof stdClass)) {
+ // assume row is the GUID
+ $row = array('guid' => $row);
+ }
+ $row = (array) $row;
+ if (empty($row['guid'])) {
+ throw new InvalidArgumentException('$row must be or contain a GUID');
+ }
+
+ // these must be present to support isFullyLoaded()
+ foreach (array('tables_split', 'tables_loaded') as $key) {
+ if (isset($this->initialized_attributes[$key])) {
+ $row[$key] = $this->initialized_attributes[$key];
+ }
+ }
+
+ $was_missing_primaries = $this->isMissingPrimaries($row);
+ $was_missing_secondaries = $this->isMissingSecondaries($row);
+
+ // some types have a function to load all attributes at once, it should be faster
+ if (($was_missing_primaries || $was_missing_secondaries) && is_callable($this->full_loader)) {
+ $fetched = (array) call_user_func($this->full_loader, $row['guid']);
+ if (!$fetched) {
+ return array();
+ }
+ $row = array_merge($row, $fetched);
+ $this->checkType($row);
+ } else {
+ if ($was_missing_primaries) {
+ if (!is_callable($this->primary_loader)) {
+ throw new LogicException('Primary attribute loader must be callable');
+ }
+ if ($this->requires_access_control) {
+ $fetched = (array) call_user_func($this->primary_loader, $row['guid']);
+ } else {
+ $ignoring_access = elgg_set_ignore_access();
+ $fetched = (array) call_user_func($this->primary_loader, $row['guid']);
+ elgg_set_ignore_access($ignoring_access);
+ }
+ if (!$fetched) {
+ return array();
+ }
+ $row = array_merge($row, $fetched);
+ }
+
+ // We must test type before trying to load the secondaries so that InvalidClassException
+ // gets thrown. Otherwise the secondary loader will fail and return false.
+ $this->checkType($row);
+
+ if ($was_missing_secondaries) {
+ if (!is_callable($this->secondary_loader)) {
+ throw new LogicException('Secondary attribute loader must be callable');
+ }
+ $fetched = (array) call_user_func($this->secondary_loader, $row['guid']);
+ if (!$fetched) {
+ if ($row['type'] === 'site') {
+ // A special case is needed for sites: When vanilla ElggEntities are created and
+ // saved, these are stored w/ type "site", but with no sites_entity row. These
+ // are probably only created in the unit tests.
+ // @todo Don't save vanilla ElggEntities with type "site"
+
+ $row = $this->filterAddedColumns($row);
+ $row['guid'] = (int) $row['guid'];
+ return $row;
+ }
+ throw new IncompleteEntityException("Secondary loader failed to return row for {$row['guid']}");
+ }
+ $row = array_merge($row, $fetched);
+ }
+ }
+
+ $row = $this->filterAddedColumns($row);
+
+ // Note: If there are still missing attributes, we're running on a 1.7 or earlier schema. We let
+ // this pass so the upgrades can run.
+
+ // guid needs to be an int https://github.com/elgg/elgg/issues/4111
+ $row['guid'] = (int) $row['guid'];
+
+ return $row;
+ }
+
+ /**
+ * Filter out keys returned by the query which should not appear in the entity's attributes
+ *
+ * @param array $row All columns from the query
+ * @return array Columns acceptable for the entity's attributes
+ */
+ protected function filterAddedColumns($row) {
+ // make an array with keys as acceptable attribute names
+ $acceptable_attrs = self::$primary_attr_names;
+ array_splice($acceptable_attrs, count($acceptable_attrs), 0, $this->secondary_attr_names);
+ $acceptable_attrs = array_combine($acceptable_attrs, $acceptable_attrs);
+
+ // @todo remove these when #4584 is in place
+ $acceptable_attrs['tables_split'] = true;
+ $acceptable_attrs['tables_loaded'] = true;
+
+ foreach ($row as $key => $val) {
+ if (!isset($acceptable_attrs[$key])) {
+ unset($row[$key]);
+ }
+ }
+ return $row;
+ }
+}
diff --git a/engine/classes/ElggAutoP.php b/engine/classes/ElggAutoP.php
new file mode 100644
index 000000000..05842d1b2
--- /dev/null
+++ b/engine/classes/ElggAutoP.php
@@ -0,0 +1,336 @@
+<?php
+
+/**
+ * Create wrapper P and BR elements in HTML depending on newlines. Useful when
+ * users use newlines to signal line and paragraph breaks. In all cases output
+ * should be well-formed markup.
+ *
+ * In DIV elements, Ps are only added when there would be at
+ * least two of them.
+ *
+ * @package Elgg.Core
+ * @subpackage Output
+ */
+class ElggAutoP {
+
+ public $encoding = 'UTF-8';
+
+ /**
+ * @var DOMDocument
+ */
+ protected $_doc = null;
+
+ /**
+ * @var DOMXPath
+ */
+ protected $_xpath = null;
+
+ protected $_blocks = 'address article area aside blockquote caption col colgroup dd
+ details div dl dt fieldset figure figcaption footer form h1 h2 h3 h4 h5 h6 header
+ hr hgroup legend map math menu nav noscript p pre section select style summary
+ table tbody td tfoot th thead tr ul ol option li';
+
+ /**
+ * @var array
+ */
+ protected $_inlines = 'a abbr audio b button canvas caption cite code command datalist
+ del dfn em embed i iframe img input ins kbd keygen label map mark meter object
+ output progress q rp rt ruby s samp script select small source span strong style
+ sub sup textarea time var video wbr';
+
+ /**
+ * Descend into these elements to add Ps
+ *
+ * @var array
+ */
+ protected $_descendList = 'article aside blockquote body details div footer form
+ header section';
+
+ /**
+ * Add Ps inside these elements
+ *
+ * @var array
+ */
+ protected $_alterList = 'article aside blockquote body details div footer header
+ section';
+
+ /** @var string */
+ protected $_unique = '';
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ $this->_blocks = preg_split('@\\s+@', $this->_blocks);
+ $this->_descendList = preg_split('@\\s+@', $this->_descendList);
+ $this->_alterList = preg_split('@\\s+@', $this->_alterList);
+ $this->_inlines = preg_split('@\\s+@', $this->_inlines);
+ $this->_unique = md5(__FILE__);
+ }
+
+ /**
+ * Intance of class for singleton pattern.
+ * @var ElggAutoP
+ */
+ private static $instance;
+
+ /**
+ * Singleton pattern.
+ * @return ElggAutoP
+ */
+ public static function getInstance() {
+ $className = __CLASS__;
+ if (!(self::$instance instanceof $className)) {
+ self::$instance = new $className();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Create wrapper P and BR elements in HTML depending on newlines. Useful when
+ * users use newlines to signal line and paragraph breaks. In all cases output
+ * should be well-formed markup.
+ *
+ * In DIV, LI, TD, and TH elements, Ps are only added when their would be at
+ * least two of them.
+ *
+ * @param string $html snippet
+ * @return string|false output or false if parse error occurred
+ */
+ public function process($html) {
+ // normalize whitespace
+ $html = str_replace(array("\r\n", "\r"), "\n", $html);
+
+ // allows preserving entities untouched
+ $html = str_replace('&', $this->_unique . 'AMP', $html);
+
+ $this->_doc = new DOMDocument();
+
+ // parse to DOM, suppressing loadHTML warnings
+ // http://www.php.net/manual/en/domdocument.loadhtml.php#95463
+ libxml_use_internal_errors(true);
+
+ // Do not load entities. May be unnecessary, better safe than sorry
+ $disable_load_entities = libxml_disable_entity_loader(true);
+
+ if (!$this->_doc->loadHTML("<html><meta http-equiv='content-type' "
+ . "content='text/html; charset={$this->encoding}'><body>{$html}</body>"
+ . "</html>")) {
+
+ libxml_disable_entity_loader($disable_load_entities);
+ return false;
+ }
+
+ libxml_disable_entity_loader($disable_load_entities);
+
+ $this->_xpath = new DOMXPath($this->_doc);
+ // start processing recursively at the BODY element
+ $nodeList = $this->_xpath->query('//body[1]');
+ $this->addParagraphs($nodeList->item(0));
+
+ // serialize back to HTML
+ $html = $this->_doc->saveHTML();
+
+ // Note: we create <autop> elements, which will later be converted to paragraphs
+
+ // split AUTOPs into multiples at /\n\n+/
+ $html = preg_replace('/(' . $this->_unique . 'NL){2,}/', '</autop><autop>', $html);
+ $html = str_replace(array($this->_unique . 'BR', $this->_unique . 'NL', '<br>'),
+ '<br />',
+ $html);
+ $html = str_replace('<br /></autop>', '</autop>', $html);
+
+ // re-parse so we can handle new AUTOP elements
+
+ // Do not load entities. May be unnecessary, better safe than sorry
+ $disable_load_entities = libxml_disable_entity_loader(true);
+
+ if (!$this->_doc->loadHTML($html)) {
+ libxml_disable_entity_loader($disable_load_entities);
+ return false;
+ }
+
+ libxml_disable_entity_loader($disable_load_entities);
+
+ // must re-create XPath object after DOM load
+ $this->_xpath = new DOMXPath($this->_doc);
+
+ // strip AUTOPs that only have comments/whitespace
+ foreach ($this->_xpath->query('//autop') as $autop) {
+ /* @var DOMElement $autop */
+ $hasContent = false;
+ if (trim($autop->textContent) !== '') {
+ $hasContent = true;
+ } else {
+ foreach ($autop->childNodes as $node) {
+ if ($node->nodeType === XML_ELEMENT_NODE) {
+ $hasContent = true;
+ break;
+ }
+ }
+ }
+ if (!$hasContent) {
+ // mark to be later replaced w/ preg_replace (faster than moving nodes out)
+ $autop->setAttribute("r", "1");
+ }
+ }
+
+ // If a DIV contains a single AUTOP, remove it
+ foreach ($this->_xpath->query('//div') as $el) {
+ /* @var DOMElement $el */
+ $autops = $this->_xpath->query('./autop', $el);
+ if ($autops->length === 1) {
+ $firstAutop = $autops->item(0);
+ /* @var DOMElement $firstAutop */
+ $firstAutop->setAttribute("r", "1");
+ }
+ }
+
+ $html = $this->_doc->saveHTML();
+
+ // trim to the contents of BODY
+ $bodyStart = strpos($html, '<body>');
+ $bodyEnd = strpos($html, '</body>', $bodyStart + 6);
+ $html = substr($html, $bodyStart + 6, $bodyEnd - $bodyStart - 6);
+
+ // strip AUTOPs that should be removed
+ $html = preg_replace('@<autop r="1">(.*?)</autop>@', '\\1', $html);
+
+ // commit to converting AUTOPs to Ps
+ $html = str_replace('<autop>', "\n<p>", $html);
+ $html = str_replace('</autop>', "</p>\n", $html);
+
+ $html = str_replace('<br>', '<br />', $html);
+ $html = str_replace($this->_unique . 'AMP', '&', $html);
+ return $html;
+ }
+
+ /**
+ * Add P and BR elements as necessary
+ *
+ * @param DOMElement $el DOM element
+ * @return void
+ */
+ protected function addParagraphs(DOMElement $el) {
+ // no need to call recursively, just queue up
+ $elsToProcess = array($el);
+ $inlinesToProcess = array();
+ while ($el = array_shift($elsToProcess)) {
+ // if true, we can alter all child nodes, if not, we'll just call
+ // addParagraphs on each element in the descendInto list
+ $alterInline = in_array($el->nodeName, $this->_alterList);
+
+ // inside affected elements, we want to trim leading whitespace from
+ // the first text node
+ $ltrimFirstTextNode = true;
+
+ // should we open a new AUTOP element to move inline elements into?
+ $openP = true;
+ $autop = null;
+
+ // after BR, ignore a newline
+ $isFollowingBr = false;
+
+ $node = $el->firstChild;
+ while (null !== $node) {
+ if ($alterInline) {
+ if ($openP) {
+ $openP = false;
+ // create a P to move inline content into (this may be removed later)
+ $autop = $el->insertBefore($this->_doc->createElement('autop'), $node);
+ }
+ }
+
+ $isElement = ($node->nodeType === XML_ELEMENT_NODE);
+ if ($isElement) {
+ $isBlock = in_array($node->nodeName, $this->_blocks);
+ } else {
+ $isBlock = false;
+ }
+
+ if ($alterInline) {
+ $isText = ($node->nodeType === XML_TEXT_NODE);
+ $isLastInline = (! $node->nextSibling
+ || ($node->nextSibling->nodeType === XML_ELEMENT_NODE
+ && in_array($node->nextSibling->nodeName, $this->_blocks)));
+ if ($isElement) {
+ $isFollowingBr = ($node->nodeName === 'br');
+ }
+
+ if ($isText) {
+ $nodeText = $node->nodeValue;
+ if ($ltrimFirstTextNode) {
+ $nodeText = ltrim($nodeText);
+ $ltrimFirstTextNode = false;
+ }
+ if ($isFollowingBr && preg_match('@^[ \\t]*\\n[ \\t]*@', $nodeText, $m)) {
+ // if a user ends a line with <br>, don't add a second BR
+ $nodeText = substr($nodeText, strlen($m[0]));
+ }
+ if ($isLastInline) {
+ $nodeText = rtrim($nodeText);
+ }
+ $nodeText = str_replace("\n", $this->_unique . 'NL', $nodeText);
+ $tmpNode = $node;
+ $node = $node->nextSibling; // move loop to next node
+
+ // alter node in place, then move into AUTOP
+ $tmpNode->nodeValue = $nodeText;
+ $autop->appendChild($tmpNode);
+
+ continue;
+ }
+ }
+ if ($isBlock || ! $node->nextSibling) {
+ if ($isBlock) {
+ if (in_array($node->nodeName, $this->_descendList)) {
+ $elsToProcess[] = $node;
+ //$this->addParagraphs($node);
+ }
+ }
+ $openP = true;
+ $ltrimFirstTextNode = true;
+ }
+ if ($alterInline) {
+ if (! $isBlock) {
+ $tmpNode = $node;
+ if ($isElement && false !== strpos($tmpNode->textContent, "\n")) {
+ $inlinesToProcess[] = $tmpNode;
+ }
+ $node = $node->nextSibling;
+ $autop->appendChild($tmpNode);
+ continue;
+ }
+ }
+
+ $node = $node->nextSibling;
+ }
+ }
+
+ // handle inline nodes
+ // no need to recurse, just queue up
+ while ($el = array_shift($inlinesToProcess)) {
+ $ignoreLeadingNewline = false;
+ foreach ($el->childNodes as $node) {
+ if ($node->nodeType === XML_ELEMENT_NODE) {
+ if ($node->nodeValue === 'BR') {
+ $ignoreLeadingNewline = true;
+ } else {
+ $ignoreLeadingNewline = false;
+ if (false !== strpos($node->textContent, "\n")) {
+ $inlinesToProcess[] = $node;
+ }
+ }
+ continue;
+ } elseif ($node->nodeType === XML_TEXT_NODE) {
+ $text = $node->nodeValue;
+ if ($text[0] === "\n" && $ignoreLeadingNewline) {
+ $text = substr($text, 1);
+ $ignoreLeadingNewline = false;
+ }
+ $node->nodeValue = str_replace("\n", $this->_unique . 'BR', $text);
+ }
+ }
+ }
+ }
+}
diff --git a/engine/classes/ElggBatch.php b/engine/classes/ElggBatch.php
new file mode 100644
index 000000000..d810ea066
--- /dev/null
+++ b/engine/classes/ElggBatch.php
@@ -0,0 +1,433 @@
+<?php
+/**
+ * Efficiently run operations on batches of results for any function
+ * that supports an options array.
+ *
+ * This is usually used with elgg_get_entities() and friends,
+ * elgg_get_annotations(), and elgg_get_metadata().
+ *
+ * If you pass a valid PHP callback, all results will be run through that
+ * callback. You can still foreach() through the result set after. Valid
+ * PHP callbacks can be a string, an array, or a closure.
+ * {@link http://php.net/manual/en/language.pseudo-types.php}
+ *
+ * The callback function must accept 3 arguments: an entity, the getter
+ * used, and the options used.
+ *
+ * Results from the callback are stored in callbackResult. If the callback
+ * returns only booleans, callbackResults will be the combined result of
+ * all calls. If no entities are processed, callbackResults will be null.
+ *
+ * If the callback returns anything else, callbackresult will be an indexed
+ * array of whatever the callback returns. If returning error handling
+ * information, you should include enough information to determine which
+ * result you're referring to.
+ *
+ * Don't combine returning bools and returning something else.
+ *
+ * Note that returning false will not stop the foreach.
+ *
+ * @warning If your callback or foreach loop deletes or disable entities
+ * you MUST call setIncrementOffset(false) or set that when instantiating.
+ * This forces the offset to stay what it was in the $options array.
+ *
+ * @example
+ * <code>
+ * // using foreach
+ * $batch = new ElggBatch('elgg_get_entities', array());
+ * $batch->setIncrementOffset(false);
+ *
+ * foreach ($batch as $entity) {
+ * $entity->disable();
+ * }
+ *
+ * // using both a callback
+ * $callback = function($result, $getter, $options) {
+ * var_dump("Looking at annotation id: $result->id");
+ * return true;
+ * }
+ *
+ * $batch = new ElggBatch('elgg_get_annotations', array('guid' => 2), $callback);
+ * </code>
+ *
+ * @package Elgg.Core
+ * @subpackage DataModel
+ * @link http://docs.elgg.org/DataModel/ElggBatch
+ * @since 1.8
+ */
+class ElggBatch
+ implements Iterator {
+
+ /**
+ * The objects to interator over.
+ *
+ * @var array
+ */
+ private $results = array();
+
+ /**
+ * The function used to get results.
+ *
+ * @var mixed A string, array, or closure, or lamda function
+ */
+ private $getter = null;
+
+ /**
+ * The number of results to grab at a time.
+ *
+ * @var int
+ */
+ private $chunkSize = 25;
+
+ /**
+ * A callback function to pass results through.
+ *
+ * @var mixed A string, array, or closure, or lamda function
+ */
+ private $callback = null;
+
+ /**
+ * Start after this many results.
+ *
+ * @var int
+ */
+ private $offset = 0;
+
+ /**
+ * Stop after this many results.
+ *
+ * @var int
+ */
+ private $limit = 0;
+
+ /**
+ * Number of processed results.
+ *
+ * @var int
+ */
+ private $retrievedResults = 0;
+
+ /**
+ * The index of the current result within the current chunk
+ *
+ * @var int
+ */
+ private $resultIndex = 0;
+
+ /**
+ * The index of the current chunk
+ *
+ * @var int
+ */
+ private $chunkIndex = 0;
+
+ /**
+ * The number of results iterated through
+ *
+ * @var int
+ */
+ private $processedResults = 0;
+
+ /**
+ * Is the getter a valid callback
+ *
+ * @var bool
+ */
+ private $validGetter = null;
+
+ /**
+ * The result of running all entities through the callback function.
+ *
+ * @var mixed
+ */
+ public $callbackResult = null;
+
+ /**
+ * If false, offset will not be incremented. This is used for callbacks/loops that delete.
+ *
+ * @var bool
+ */
+ private $incrementOffset = true;
+
+ /**
+ * Entities that could not be instantiated during a fetch
+ *
+ * @var stdClass[]
+ */
+ private $incompleteEntities = array();
+
+ /**
+ * Total number of incomplete entities fetched
+ *
+ * @var int
+ */
+ private $totalIncompletes = 0;
+
+ /**
+ * Batches operations on any elgg_get_*() or compatible function that supports
+ * an options array.
+ *
+ * Instead of returning all objects in memory, it goes through $chunk_size
+ * objects, then requests more from the server. This avoids OOM errors.
+ *
+ * @param string $getter The function used to get objects. Usually
+ * an elgg_get_*() function, but can be any valid PHP callback.
+ * @param array $options The options array to pass to the getter function. If limit is
+ * not set, 10 is used as the default. In most cases that is not
+ * what you want.
+ * @param mixed $callback An optional callback function that all results will be passed
+ * to upon load. The callback needs to accept $result, $getter,
+ * $options.
+ * @param int $chunk_size The number of entities to pull in before requesting more.
+ * You have to balance this between running out of memory in PHP
+ * and hitting the db server too often.
+ * @param bool $inc_offset Increment the offset on each fetch. This must be false for
+ * callbacks that delete rows. You can set this after the
+ * object is created with {@see ElggBatch::setIncrementOffset()}.
+ */
+ public function __construct($getter, $options, $callback = null, $chunk_size = 25,
+ $inc_offset = true) {
+
+ $this->getter = $getter;
+ $this->options = $options;
+ $this->callback = $callback;
+ $this->chunkSize = $chunk_size;
+ $this->setIncrementOffset($inc_offset);
+
+ if ($this->chunkSize <= 0) {
+ $this->chunkSize = 25;
+ }
+
+ // store these so we can compare later
+ $this->offset = elgg_extract('offset', $options, 0);
+ $this->limit = elgg_extract('limit', $options, 10);
+
+ // if passed a callback, create a new ElggBatch with the same options
+ // and pass each to the callback.
+ if ($callback && is_callable($callback)) {
+ $batch = new ElggBatch($getter, $options, null, $chunk_size, $inc_offset);
+
+ $all_results = null;
+
+ foreach ($batch as $result) {
+ if (is_string($callback)) {
+ $result = $callback($result, $getter, $options);
+ } else {
+ $result = call_user_func_array($callback, array($result, $getter, $options));
+ }
+
+ if (!isset($all_results)) {
+ if ($result === true || $result === false || $result === null) {
+ $all_results = $result;
+ } else {
+ $all_results = array();
+ }
+ }
+
+ if (($result === true || $result === false || $result === null) && !is_array($all_results)) {
+ $all_results = $result && $all_results;
+ } else {
+ $all_results[] = $result;
+ }
+ }
+
+ $this->callbackResult = $all_results;
+ }
+ }
+
+ /**
+ * Tell the process that an entity was incomplete during a fetch
+ *
+ * @param stdClass $row
+ *
+ * @access private
+ */
+ public function reportIncompleteEntity(stdClass $row) {
+ $this->incompleteEntities[] = $row;
+ }
+
+ /**
+ * Fetches the next chunk of results
+ *
+ * @return bool
+ */
+ private function getNextResultsChunk() {
+
+ // always reset results.
+ $this->results = array();
+
+ if (!isset($this->validGetter)) {
+ $this->validGetter = is_callable($this->getter);
+ }
+
+ if (!$this->validGetter) {
+ return false;
+ }
+
+ $limit = $this->chunkSize;
+
+ // if someone passed limit = 0 they want everything.
+ if ($this->limit != 0) {
+ if ($this->retrievedResults >= $this->limit) {
+ return false;
+ }
+
+ // if original limit < chunk size, set limit to original limit
+ // else if the number of results we'll fetch if greater than the original limit
+ if ($this->limit < $this->chunkSize) {
+ $limit = $this->limit;
+ } elseif ($this->retrievedResults + $this->chunkSize > $this->limit) {
+ // set the limit to the number of results remaining in the original limit
+ $limit = $this->limit - $this->retrievedResults;
+ }
+ }
+
+ if ($this->incrementOffset) {
+ $offset = $this->offset + $this->retrievedResults;
+ } else {
+ $offset = $this->offset + $this->totalIncompletes;
+ }
+
+ $current_options = array(
+ 'limit' => $limit,
+ 'offset' => $offset,
+ '__ElggBatch' => $this,
+ );
+
+ $options = array_merge($this->options, $current_options);
+
+ $this->incompleteEntities = array();
+ $this->results = call_user_func_array($this->getter, array($options));
+
+ $num_results = count($this->results);
+ $num_incomplete = count($this->incompleteEntities);
+
+ $this->totalIncompletes += $num_incomplete;
+
+ if ($this->incompleteEntities) {
+ // pad the front of the results with nulls representing the incompletes
+ array_splice($this->results, 0, 0, array_pad(array(), $num_incomplete, null));
+ // ...and skip past them
+ reset($this->results);
+ for ($i = 0; $i < $num_incomplete; $i++) {
+ next($this->results);
+ }
+ }
+
+ if ($this->results) {
+ $this->chunkIndex++;
+
+ // let the system know we've jumped past the nulls
+ $this->resultIndex = $num_incomplete;
+
+ $this->retrievedResults += ($num_results + $num_incomplete);
+ if ($num_results == 0) {
+ // This fetch was *all* incompletes! We need to fetch until we can either
+ // offer at least one row to iterate over, or give up.
+ return $this->getNextResultsChunk();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Increment the offset from the original options array? Setting to
+ * false is required for callbacks that delete rows.
+ *
+ * @param bool $increment Set to false when deleting data
+ * @return void
+ */
+ public function setIncrementOffset($increment = true) {
+ $this->incrementOffset = (bool) $increment;
+ }
+
+ /**
+ * Implements Iterator
+ */
+
+ /**
+ * PHP Iterator Interface
+ *
+ * @see Iterator::rewind()
+ * @return void
+ */
+ public function rewind() {
+ $this->resultIndex = 0;
+ $this->retrievedResults = 0;
+ $this->processedResults = 0;
+
+ // only grab results if we haven't yet or we're crossing chunks
+ if ($this->chunkIndex == 0 || $this->limit > $this->chunkSize) {
+ $this->chunkIndex = 0;
+ $this->getNextResultsChunk();
+ }
+ }
+
+ /**
+ * PHP Iterator Interface
+ *
+ * @see Iterator::current()
+ * @return mixed
+ */
+ public function current() {
+ return current($this->results);
+ }
+
+ /**
+ * PHP Iterator Interface
+ *
+ * @see Iterator::key()
+ * @return int
+ */
+ public function key() {
+ return $this->processedResults;
+ }
+
+ /**
+ * PHP Iterator Interface
+ *
+ * @see Iterator::next()
+ * @return mixed
+ */
+ public function next() {
+ // if we'll be at the end.
+ if (($this->processedResults + 1) >= $this->limit && $this->limit > 0) {
+ $this->results = array();
+ return false;
+ }
+
+ // if we'll need new results.
+ if (($this->resultIndex + 1) >= $this->chunkSize) {
+ if (!$this->getNextResultsChunk()) {
+ $this->results = array();
+ return false;
+ }
+
+ $result = current($this->results);
+ } else {
+ // the function above resets the indexes, so only inc if not
+ // getting new set
+ $this->resultIndex++;
+ $result = next($this->results);
+ }
+
+ $this->processedResults++;
+ return $result;
+ }
+
+ /**
+ * PHP Iterator Interface
+ *
+ * @see Iterator::valid()
+ * @return bool
+ */
+ public function valid() {
+ if (!is_array($this->results)) {
+ return false;
+ }
+ $key = key($this->results);
+ return ($key !== NULL && $key !== FALSE);
+ }
+}
diff --git a/engine/classes/ElggCache.php b/engine/classes/ElggCache.php
index 549772d6d..909eab39b 100644
--- a/engine/classes/ElggCache.php
+++ b/engine/classes/ElggCache.php
@@ -6,9 +6,7 @@
* @package Elgg.Core
* @subpackage Cache
*/
-abstract class ElggCache implements
- // Override for array access
- ArrayAccess {
+abstract class ElggCache implements ArrayAccess {
/**
* Variables for the cache object.
*
@@ -23,6 +21,7 @@ abstract class ElggCache implements
$this->variables = array();
}
+ // @codingStandardsIgnoreStart
/**
* Set a cache variable.
*
@@ -31,12 +30,13 @@ abstract class ElggCache implements
*
* @return void
*
- * @deprecated 1.8 Use ElggAccess:setVariable()
+ * @deprecated 1.8 Use ElggCache:setVariable()
*/
public function set_variable($variable, $value) {
elgg_deprecated_notice('ElggCache::set_variable() is deprecated by ElggCache::setVariable()', 1.8);
$this->setVariable($variable, $value);
}
+ // @codingStandardsIgnoreEnd
/**
* Set a cache variable.
@@ -54,6 +54,7 @@ abstract class ElggCache implements
$this->variables[$variable] = $value;
}
+ // @codingStandardsIgnoreStart
/**
* Get variables for this cache.
*
@@ -67,6 +68,7 @@ abstract class ElggCache implements
elgg_deprecated_notice('ElggCache::get_variable() is deprecated by ElggCache::getVariable()', 1.8);
return $this->getVariable($variable);
}
+ // @codingStandardsIgnoreEnd
/**
* Get variables for this cache.
@@ -141,6 +143,9 @@ abstract class ElggCache implements
/**
* Load data from the cache using a given key.
*
+ * @todo $offset is a horrible variable name because it creates confusion
+ * with the ArrayAccess methods
+ *
* @param string $key Name
* @param int $offset Offset
* @param int $limit Limit
@@ -186,12 +191,12 @@ abstract class ElggCache implements
// ARRAY ACCESS INTERFACE //////////////////////////////////////////////////////////
/**
- * Set offset
+ * Assigns a value for the specified key
*
* @see ArrayAccess::offsetSet()
*
- * @param mixed $key Name
- * @param mixed $value Value
+ * @param mixed $key The key (offset) to assign the value to.
+ * @param mixed $value The value to set.
*
* @return void
*/
@@ -200,43 +205,43 @@ abstract class ElggCache implements
}
/**
- * Get offset
+ * Get the value for specified key
*
* @see ArrayAccess::offsetGet()
*
- * @param mixed $key Name
+ * @param mixed $key The key (offset) to retrieve.
*
- * @return void
+ * @return mixed
*/
function offsetGet($key) {
return $this->load($key);
}
/**
- * Unsets offset
+ * Unsets a key.
*
* @see ArrayAccess::offsetUnset()
*
- * @param mixed $key Name
+ * @param mixed $key The key (offset) to unset.
*
* @return void
*/
function offsetUnset($key) {
- if (isset($this->key)) {
- unset($this->key);
+ if (isset($this->$key)) {
+ unset($this->$key);
}
}
/**
- * Does offset exist
+ * Does key exist
*
* @see ArrayAccess::offsetExists()
*
- * @param mixed $offset Offset
+ * @param mixed $key A key (offset) to check for.
*
- * @return void
+ * @return bool
*/
- function offsetExists($offset) {
- return isset($this->$offset);
+ function offsetExists($key) {
+ return isset($this->$key);
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggCrypto.php b/engine/classes/ElggCrypto.php
new file mode 100644
index 000000000..317d371e4
--- /dev/null
+++ b/engine/classes/ElggCrypto.php
@@ -0,0 +1,208 @@
+<?php
+/**
+ * ElggCrypto
+ *
+ * @package Elgg.Core
+ * @subpackage Crypto
+ *
+ * @access private
+ */
+class ElggCrypto {
+
+ /**
+ * Character set for temp passwords (no risk of embedded profanity/glyphs that look similar)
+ */
+ const CHARS_PASSWORD = 'bcdfghjklmnpqrstvwxyz2346789';
+
+ /**
+ * Generate a string of highly randomized bytes (over the full 8-bit range).
+ *
+ * @param int $length Number of bytes needed
+ * @return string Random bytes
+ *
+ * @author George Argyros <argyros.george@gmail.com>
+ * @copyright 2012, George Argyros. All rights reserved.
+ * @license Modified BSD
+ * @link https://github.com/GeorgeArgyros/Secure-random-bytes-in-PHP/blob/master/srand.php Original
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL GEORGE ARGYROS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ public function getRandomBytes($length) {
+ /**
+ * Our primary choice for a cryptographic strong randomness function is
+ * openssl_random_pseudo_bytes.
+ */
+ $SSLstr = '4'; // http://xkcd.com/221/
+ if (function_exists('openssl_random_pseudo_bytes')
+ && (version_compare(PHP_VERSION, '5.3.4') >= 0 || substr(PHP_OS, 0, 3) !== 'WIN')) {
+ $SSLstr = openssl_random_pseudo_bytes($length, $strong);
+ if ($strong) {
+ return $SSLstr;
+ }
+ }
+
+ /**
+ * If mcrypt extension is available then we use it to gather entropy from
+ * the operating system's PRNG. This is better than reading /dev/urandom
+ * directly since it avoids reading larger blocks of data than needed.
+ * Older versions of mcrypt_create_iv may be broken or take too much time
+ * to finish so we only use this function with PHP 5.3.7 and above.
+ * @see https://bugs.php.net/bug.php?id=55169
+ */
+ if (function_exists('mcrypt_create_iv')
+ && (version_compare(PHP_VERSION, '5.3.7') >= 0 || substr(PHP_OS, 0, 3) !== 'WIN')) {
+ $str = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
+ if ($str !== false) {
+ return $str;
+ }
+ }
+
+ /**
+ * No build-in crypto randomness function found. We collect any entropy
+ * available in the PHP core PRNGs along with some filesystem info and memory
+ * stats. To make this data cryptographically strong we add data either from
+ * /dev/urandom or if its unavailable, we gather entropy by measuring the
+ * time needed to compute a number of SHA-1 hashes.
+ */
+ $str = '';
+ $bits_per_round = 2; // bits of entropy collected in each clock drift round
+ $msec_per_round = 400; // expected running time of each round in microseconds
+ $hash_len = 20; // SHA-1 Hash length
+ $total = $length; // total bytes of entropy to collect
+
+ $handle = @fopen('/dev/urandom', 'rb');
+ if ($handle && function_exists('stream_set_read_buffer')) {
+ @stream_set_read_buffer($handle, 0);
+ }
+
+ do {
+ $bytes = ($total > $hash_len) ? $hash_len : $total;
+ $total -= $bytes;
+
+ //collect any entropy available from the PHP system and filesystem
+ $entropy = rand() . uniqid(mt_rand(), true) . $SSLstr;
+ $entropy .= implode('', @fstat(@fopen(__FILE__, 'r')));
+ $entropy .= memory_get_usage() . getmypid();
+ $entropy .= serialize($_ENV) . serialize($_SERVER);
+ if (function_exists('posix_times')) {
+ $entropy .= serialize(posix_times());
+ }
+ if (function_exists('zend_thread_id')) {
+ $entropy .= zend_thread_id();
+ }
+
+ if ($handle) {
+ $entropy .= @fread($handle, $bytes);
+ } else {
+ // Measure the time that the operations will take on average
+ for ($i = 0; $i < 3; $i++) {
+ $c1 = microtime(true);
+ $var = sha1(mt_rand());
+ for ($j = 0; $j < 50; $j++) {
+ $var = sha1($var);
+ }
+ $c2 = microtime(true);
+ $entropy .= $c1 . $c2;
+ }
+
+ // Based on the above measurement determine the total rounds
+ // in order to bound the total running time.
+ $rounds = (int) ($msec_per_round * 50 / (int) (($c2 - $c1) * 1000000));
+
+ // Take the additional measurements. On average we can expect
+ // at least $bits_per_round bits of entropy from each measurement.
+ $iter = $bytes * (int) (ceil(8 / $bits_per_round));
+
+ for ($i = 0; $i < $iter; $i++) {
+ $c1 = microtime();
+ $var = sha1(mt_rand());
+ for ($j = 0; $j < $rounds; $j++) {
+ $var = sha1($var);
+ }
+ $c2 = microtime();
+ $entropy .= $c1 . $c2;
+ }
+ }
+
+ // We assume sha1 is a deterministic extractor for the $entropy variable.
+ $str .= sha1($entropy, true);
+
+ } while ($length > strlen($str));
+
+ if ($handle) {
+ @fclose($handle);
+ }
+
+ return substr($str, 0, $length);
+ }
+
+ /**
+ * Generate a random string of specified length.
+ *
+ * Uses supplied character list for generating the new string.
+ * If no character list provided - uses Base64 URL character set.
+ *
+ * @param int $length Desired length of the string
+ * @param string|null $chars Characters to be chosen from randomly. If not given, the Base64 URL
+ * charset will be used.
+ *
+ * @return string The random string
+ *
+ * @throws InvalidArgumentException
+ *
+ * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ *
+ * @see https://github.com/zendframework/zf2/blob/master/library/Zend/Math/Rand.php#L179
+ */
+ public static function getRandomString($length, $chars = null) {
+ if ($length < 1) {
+ throw new InvalidArgumentException('Length should be >= 1');
+ }
+
+ if (empty($chars)) {
+ $numBytes = ceil($length * 0.75);
+ $bytes = self::getRandomBytes($numBytes);
+ $string = substr(rtrim(base64_encode($bytes), '='), 0, $length);
+
+ // Base64 URL
+ return strtr($string, '+/', '-_');
+ }
+
+ $listLen = strlen($chars);
+
+ if ($listLen == 1) {
+ return str_repeat($chars, $length);
+ }
+
+ $bytes = self::getRandomBytes($length);
+ $pos = 0;
+ $result = '';
+ for ($i = 0; $i < $length; $i++) {
+ $pos = ($pos + ord($bytes[$i])) % $listLen;
+ $result .= $chars[$pos];
+ }
+
+ return $result;
+ }
+}
diff --git a/engine/classes/ElggData.php b/engine/classes/ElggData.php
index 2853a5298..4f843cde4 100644
--- a/engine/classes/ElggData.php
+++ b/engine/classes/ElggData.php
@@ -5,6 +5,9 @@
*
* @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
@@ -23,6 +26,7 @@ abstract class ElggData implements
*/
protected $attributes = array();
+ // @codingStandardsIgnoreStart
/**
* Initialise the attributes array.
*
@@ -33,16 +37,15 @@ abstract class ElggData implements
* Passing false returns false. Core constructors always pass false.
* Does nothing either way since attributes are initialized by the time
* this is called.
- * @return false|void False is
+ * @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);
- } else {
- return false;
}
}
+ // @codingStandardsIgnoreEnd
/**
* Initialize the attributes array.
@@ -111,7 +114,7 @@ abstract class ElggData implements
* @param string $name The attribute to set
* @param mixed $value The value to set it to
*
- * @return The success of your set funtion?
+ * @return bool The success of your set function?
*/
abstract protected function set($name, $value);
@@ -128,7 +131,7 @@ abstract class ElggData implements
* @return bool
*/
abstract public function save();
-
+
/**
* Delete this data.
*
@@ -195,7 +198,7 @@ abstract class ElggData implements
*
* @see Iterator::current()
*
- * @return void
+ * @return mixed
*/
public function current() {
return current($this->attributes);
@@ -206,7 +209,7 @@ abstract class ElggData implements
*
* @see Iterator::key()
*
- * @return void
+ * @return string
*/
public function key() {
return key($this->attributes);
@@ -228,7 +231,7 @@ abstract class ElggData implements
*
* @see Iterator::valid()
*
- * @return void
+ * @return bool
*/
public function valid() {
return $this->valid;
@@ -266,12 +269,13 @@ abstract class ElggData implements
*
* @param mixed $key Name
*
- * @return void
+ * @return mixed
*/
public function offsetGet($key) {
if (array_key_exists($key, $this->attributes)) {
return $this->attributes[$key];
}
+ return null;
}
/**
@@ -302,4 +306,4 @@ abstract class ElggData implements
public function offsetExists($offset) {
return array_key_exists($offset, $this->attributes);
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggDiskFilestore.php b/engine/classes/ElggDiskFilestore.php
index c7ca9fd05..6e2354012 100644
--- a/engine/classes/ElggDiskFilestore.php
+++ b/engine/classes/ElggDiskFilestore.php
@@ -60,10 +60,11 @@ class ElggDiskFilestore extends ElggFilestore {
$path = substr($fullname, 0, $ls);
$name = substr($fullname, $ls);
+ // @todo $name is unused, remove it or do we need to fix something?
// Try and create the directory
try {
- $this->make_directory_root($path);
+ $this->makeDirectoryRoot($path);
} catch (Exception $e) {
}
@@ -108,7 +109,7 @@ class ElggDiskFilestore extends ElggFilestore {
*
* @param resource $f File pointer resource
* @param int $length The number of bytes to read
- * @param inf $offset The number of bytes to start after
+ * @param int $offset The number of bytes to start after
*
* @return mixed Contents of file or false on fail.
*/
@@ -193,25 +194,33 @@ class ElggDiskFilestore extends ElggFilestore {
}
/**
- * Returns the filename as saved on disk for an ElggFile object
+ * Get the filename as saved on disk for an ElggFile object
+ *
+ * Returns an empty string if no filename set
*
* @param ElggFile $file File object
*
* @return string The full path of where the file is stored
+ * @throws InvalidParameterException
*/
public function getFilenameOnFilestore(ElggFile $file) {
- $owner = $file->getOwnerEntity();
- if (!$owner) {
- $owner = get_loggedin_user();
+ $owner_guid = $file->getOwnerGuid();
+ if (!$owner_guid) {
+ $owner_guid = elgg_get_logged_in_user_guid();
}
- if ((!$owner) || (!$owner->username)) {
+ if (!$owner_guid) {
$msg = elgg_echo('InvalidParameterException:MissingOwner',
array($file->getFilename(), $file->guid));
throw new InvalidParameterException($msg);
}
- return $this->dir_root . $this->makefileMatrix($owner->guid) . $file->getFilename();
+ $filename = $file->getFilename();
+ if (!$filename) {
+ return '';
+ }
+
+ return $this->dir_root . $this->makeFileMatrix($owner_guid) . $filename;
}
/**
@@ -219,7 +228,7 @@ class ElggDiskFilestore extends ElggFilestore {
*
* @param ElggFile $file File object
*
- * @return mixed
+ * @return string
*/
public function grabFile(ElggFile $file) {
return file_get_contents($file->getFilenameOnFilestore());
@@ -233,6 +242,9 @@ class ElggDiskFilestore extends ElggFilestore {
* @return bool
*/
public function exists(ElggFile $file) {
+ if (!$file->getFilename()) {
+ return false;
+ }
return file_exists($this->getFilenameOnFilestore($file));
}
@@ -246,12 +258,13 @@ class ElggDiskFilestore extends ElggFilestore {
*/
public function getSize($prefix = '', $container_guid) {
if ($container_guid) {
- return get_dir_size($this->dir_root . $this->makefileMatrix($container_guid) . $prefix);
+ return get_dir_size($this->dir_root . $this->makeFileMatrix($container_guid) . $prefix);
} else {
return false;
}
}
+ // @codingStandardsIgnoreStart
/**
* Create a directory $dirroot
*
@@ -266,6 +279,7 @@ class ElggDiskFilestore extends ElggFilestore {
return $this->makeDirectoryRoot($dirroot);
}
+ // @codingStandardsIgnoreEnd
/**
* Create a directory $dirroot
@@ -285,6 +299,7 @@ class ElggDiskFilestore extends ElggFilestore {
return true;
}
+ // @codingStandardsIgnoreStart
/**
* Multibyte string tokeniser.
*
@@ -315,30 +330,31 @@ class ElggDiskFilestore extends ElggFilestore {
} else {
return str_split($string);
}
-
- return false;
}
+ // @codingStandardsIgnoreEnd
+ // @codingStandardsIgnoreStart
/**
* Construct a file path matrix for an entity.
*
* @param int $identifier The guide of the entity to store the data under.
*
- * @return str The path where the entity's data will be stored.
+ * @return string The path where the entity's data will be stored.
* @deprecated 1.8 Use ElggDiskFilestore::makeFileMatrix()
*/
protected function make_file_matrix($identifier) {
elgg_deprecated_notice('ElggDiskFilestore::make_file_matrix() is deprecated by ::makeFileMatrix()', 1.8);
- return $this->makefileMatrix($identifier);
+ return $this->makeFileMatrix($identifier);
}
+ // @codingStandardsIgnoreEnd
/**
* Construct a file path matrix for an entity.
*
* @param int $guid The guide of the entity to store the data under.
*
- * @return str The path where the entity's data will be stored.
+ * @return string The path where the entity's data will be stored.
*/
protected function makeFileMatrix($guid) {
$entity = get_entity($guid);
@@ -352,6 +368,7 @@ class ElggDiskFilestore extends ElggFilestore {
return "$time_created/$entity->guid/";
}
+ // @codingStandardsIgnoreStart
/**
* Construct a filename matrix.
*
@@ -363,13 +380,14 @@ class ElggDiskFilestore extends ElggFilestore {
*
* @param int $guid The entity to contrust a matrix for
*
- * @return str The
+ * @return string The
*/
protected function user_file_matrix($guid) {
elgg_deprecated_notice('ElggDiskFilestore::user_file_matrix() is deprecated by ::makeFileMatrix()', 1.8);
return $this->makeFileMatrix($guid);
}
+ // @codingStandardsIgnoreEnd
/**
* Returns a list of attributes to save to the database when saving
@@ -396,4 +414,4 @@ class ElggDiskFilestore extends ElggFilestore {
return false;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggEntity.php b/engine/classes/ElggEntity.php
index fb80673dc..a563f6fad 100644
--- a/engine/classes/ElggEntity.php
+++ b/engine/classes/ElggEntity.php
@@ -24,7 +24,17 @@
*
* @package Elgg.Core
* @subpackage DataModel.Entities
- * @link http://docs.elgg.org/DataModel/ElggEntity
+ *
+ * @property string $type object, user, group, or site (read-only after save)
+ * @property string $subtype Further clarifies the nature of the entity (read-only after save)
+ * @property int $guid The unique identifier for this entity (read only)
+ * @property int $owner_guid The GUID of the creator of this entity
+ * @property int $container_guid The GUID of the entity containing this entity
+ * @property int $site_guid The GUID of the website this entity is associated with
+ * @property int $access_id Specifies the visibility level of this entity
+ * @property int $time_created A UNIX timestamp of when the entity was created (read-only, set on first save)
+ * @property int $time_updated A UNIX timestamp of when the entity was last updated (automatically updated on save)
+ * @property-read string $enabled
*/
abstract class ElggEntity extends ElggData implements
Notable, // Calendar interface
@@ -55,6 +65,12 @@ abstract class ElggEntity extends ElggData implements
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.
*/
@@ -74,11 +90,12 @@ abstract class ElggEntity extends ElggData implements
$this->attributes['type'] = NULL;
$this->attributes['subtype'] = NULL;
- $this->attributes['owner_guid'] = get_loggedin_userid();
- $this->attributes['container_guid'] = get_loggedin_userid();
+ $this->attributes['owner_guid'] = elgg_get_logged_in_user_guid();
+ $this->attributes['container_guid'] = elgg_get_logged_in_user_guid();
$this->attributes['site_guid'] = 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";
@@ -122,7 +139,10 @@ abstract class ElggEntity extends ElggData implements
return;
}
- $metadata_array = get_metadata_for_entity($this->guid);
+ $metadata_array = elgg_get_metadata(array(
+ 'guid' => $this->guid,
+ 'limit' => 0
+ ));
$this->attributes['guid'] = "";
@@ -167,7 +187,7 @@ abstract class ElggEntity extends ElggData implements
*/
public function get($name) {
// See if its in our base attributes
- if (isset($this->attributes[$name])) {
+ if (array_key_exists($name, $this->attributes)) {
return $this->attributes[$name];
}
@@ -181,8 +201,11 @@ abstract class ElggEntity extends ElggData implements
/**
* Sets the value of a property.
*
- * If $name is defined in $this->attributes that value is set, otherwise it will
- * set the appropriate item of metadata.
+ * If $name is defined in $this->attributes that value is set, otherwise it is
+ * saved as metadata.
+ *
+ * @warning Metadata set this way will inherit the entity's owner and access ID. If you want
+ * to set metadata with a different owner, use create_metadata().
*
* @warning It is important that your class populates $this->attributes with keys
* for all base attributes, anything not in their gets set as METADATA.
@@ -203,7 +226,6 @@ abstract class ElggEntity extends ElggData implements
// Certain properties should not be manually changed!
switch ($name) {
case 'guid':
- case 'time_created':
case 'time_updated':
case 'last_action':
return FALSE;
@@ -227,21 +249,53 @@ abstract class ElggEntity extends ElggData implements
* @return mixed The value, or NULL if not found.
*/
public function getMetaData($name) {
- if ((int) ($this->guid) > 0) {
- $md = get_metadata_byname($this->getGUID(), $name);
- } else {
+ $guid = $this->getGUID();
+
+ if (! $guid) {
if (isset($this->temp_metadata[$name])) {
- return $this->temp_metadata[$name];
+ // md is returned as an array only if more than 1 entry
+ if (count($this->temp_metadata[$name]) == 1) {
+ return $this->temp_metadata[$name][0];
+ } else {
+ return $this->temp_metadata[$name];
+ }
+ } else {
+ return null;
+ }
+ }
+
+ // upon first cache miss, just load/cache all the metadata and retry.
+ // if this works, the rest of this function may not be needed!
+ $cache = elgg_get_metadata_cache();
+ if ($cache->isKnown($guid, $name)) {
+ return $cache->load($guid, $name);
+ } else {
+ $cache->populateFromEntities(array($guid));
+ // in case ignore_access was on, we have to check again...
+ if ($cache->isKnown($guid, $name)) {
+ return $cache->load($guid, $name);
}
}
+ $md = elgg_get_metadata(array(
+ 'guid' => $guid,
+ 'metadata_name' => $name,
+ 'limit' => 0,
+ ));
+
+ $value = null;
+
if ($md && !is_array($md)) {
- return $md->value;
+ $value = $md->value;
+ } elseif (count($md) == 1) {
+ $value = $md[0]->value;
} else if ($md && is_array($md)) {
- return metadata_array_to_values($md);
+ $value = metadata_array_to_values($md);
}
- return null;
+ $cache->save($guid, $name, $value);
+
+ return $value;
}
/**
@@ -257,73 +311,136 @@ abstract class ElggEntity extends ElggData implements
if (array_key_exists($name, $this->attributes)) {
$this->attributes[$name] = "";
} else {
- $this->clearMetaData($name);
+ $this->deleteMetadata($name);
}
}
/**
* Set a piece of metadata.
*
- * @tip Plugin authors should use the magic methods.
+ * Plugin authors should use the magic methods or create_metadata().
+ *
+ * @warning The metadata will inherit the parent entity's owner and access ID.
+ * If you want to write metadata with a different owner, use create_metadata().
*
* @access private
*
* @param string $name Name of the metadata
- * @param mixed $value Value of the metadata
+ * @param mixed $value Value of the metadata (doesn't support assoc arrays)
* @param string $value_type Types supported: integer and string. Will auto-identify if not set
* @param bool $multiple Allow multiple values for a single name (doesn't support assoc arrays)
*
* @return bool
*/
- public function setMetaData($name, $value, $value_type = "", $multiple = false) {
+ public function setMetaData($name, $value, $value_type = null, $multiple = false) {
+
+ // normalize value to an array that we will loop over
+ // remove indexes if value already an array.
if (is_array($value)) {
- unset($this->temp_metadata[$name]);
- remove_metadata($this->getGUID(), $name);
- foreach ($value as $v) {
- if ((int) $this->guid > 0) {
- $multiple = true;
- if (!create_metadata($this->getGUID(), $name, $v, $value_type,
- $this->getOwnerGUID(), $this->getAccessID(), $multiple)) {
- return false;
- }
- } else {
- if (($multiple) && (isset($this->temp_metadata[$name]))) {
- if (!is_array($this->temp_metadata[$name])) {
- $tmp = $this->temp_metadata[$name];
- $this->temp_metadata[$name] = array();
- $this->temp_metadata[$name][] = $tmp;
- }
-
- $this->temp_metadata[$name][] = $value;
- } else {
- $this->temp_metadata[$name] = $value;
- }
+ $value = array_values($value);
+ } else {
+ $value = array($value);
+ }
+
+ // saved entity. persist md to db.
+ if ($this->guid) {
+ // if overwriting, delete first.
+ if (!$multiple) {
+ $options = array(
+ 'guid' => $this->getGUID(),
+ 'metadata_name' => $name,
+ 'limit' => 0
+ );
+ // @todo in 1.9 make this return false if can't add metadata
+ // https://github.com/elgg/elgg/issues/4520
+ //
+ // need to remove access restrictions right now to delete
+ // because this is the expected behavior
+ $ia = elgg_set_ignore_access(true);
+ if (false === elgg_delete_metadata($options)) {
+ return false;
}
+ elgg_set_ignore_access($ia);
}
- return true;
- } else {
- unset($this->temp_metadata[$name]);
- if ((int) $this->guid > 0) {
- $result = create_metadata($this->getGUID(), $name, $value, $value_type,
- $this->getOwnerGUID(), $this->getAccessID(), $multiple);
- return (bool)$result;
- } else {
- if (($multiple) && (isset($this->temp_metadata[$name]))) {
- if (!is_array($this->temp_metadata[$name])) {
- $tmp = $this->temp_metadata[$name];
- $this->temp_metadata[$name] = array();
- $this->temp_metadata[$name][] = $tmp;
- }
-
- $this->temp_metadata[$name][] = $value;
- } else {
- $this->temp_metadata[$name] = $value;
+ // add new md
+ $result = true;
+ foreach ($value as $value_tmp) {
+ // at this point $value should be appended because it was cleared above if needed.
+ $md_id = create_metadata($this->getGUID(), $name, $value_tmp, $value_type,
+ $this->getOwnerGUID(), $this->getAccessId(), true);
+ if (!$md_id) {
+ return false;
}
+ }
- return true;
+ return $result;
+ } else {
+ // unsaved entity. store in temp array
+ // returning single entries instead of an array of 1 element is decided in
+ // getMetaData(), just like pulling from the db.
+ //
+ // if overwrite, delete first
+ if (!$multiple || !isset($this->temp_metadata[$name])) {
+ $this->temp_metadata[$name] = array();
}
+
+ // add new md
+ $this->temp_metadata[$name] = array_merge($this->temp_metadata[$name], $value);
+ return true;
+ }
+ }
+
+ /**
+ * Deletes all metadata on this object (metadata.entity_guid = $this->guid).
+ * If you pass a name, only metadata matching that name will be deleted.
+ *
+ * @warning Calling this with no $name will clear all metadata on the entity.
+ *
+ * @param null|string $name The name of the metadata to remove.
+ * @return bool
+ * @since 1.8
+ */
+ public function deleteMetadata($name = null) {
+
+ if (!$this->guid) {
+ return false;
}
+
+ $options = array(
+ 'guid' => $this->guid,
+ 'limit' => 0
+ );
+ if ($name) {
+ $options['metadata_name'] = $name;
+ }
+
+ return elgg_delete_metadata($options);
+ }
+
+ /**
+ * Deletes all metadata owned by this object (metadata.owner_guid = $this->guid).
+ * If you pass a name, only metadata matching that name will be deleted.
+ *
+ * @param null|string $name The name of metadata to delete.
+ * @return bool
+ * @since 1.8
+ */
+ public function deleteOwnedMetadata($name = null) {
+ // access is turned off for this because they might
+ // no longer have access to an entity they created metadata on.
+ $ia = elgg_set_ignore_access(true);
+ $options = array(
+ 'metadata_owner_guid' => $this->guid,
+ 'limit' => 0
+ );
+ if ($name) {
+ $options['metadata_name'] = $name;
+ }
+
+ $r = elgg_delete_metadata($options);
+ elgg_set_ignore_access($ia);
+ return $r;
}
/**
@@ -332,17 +449,53 @@ abstract class ElggEntity extends ElggData implements
* @warning Calling this with no or empty arguments will clear all metadata on the entity.
*
* @param string $name The name of the metadata to clear
- *
* @return mixed bool
+ * @deprecated 1.8 Use deleteMetadata()
*/
- public function clearMetaData($name = "") {
- if (empty($name)) {
- return clear_metadata($this->getGUID());
- } else {
- return remove_metadata($this->getGUID(), $name);
+ public function clearMetaData($name = '') {
+ elgg_deprecated_notice('ElggEntity->clearMetadata() is deprecated by ->deleteMetadata()', 1.8);
+ return $this->deleteMetadata($name);
+ }
+
+ /**
+ * Disables metadata for this entity, optionally based on name.
+ *
+ * @param string $name An options name of metadata to disable.
+ * @return bool
+ * @since 1.8
+ */
+ public function disableMetadata($name = '') {
+ $options = array(
+ 'guid' => $this->guid,
+ 'limit' => 0
+ );
+ if ($name) {
+ $options['metadata_name'] = $name;
}
+
+ return elgg_disable_metadata($options);
}
+ /**
+ * Enables metadata for this entity, optionally based on name.
+ *
+ * @warning Before calling this, you must use {@link access_show_hidden_entities()}
+ *
+ * @param string $name An options name of metadata to enable.
+ * @return bool
+ * @since 1.8
+ */
+ public function enableMetadata($name = '') {
+ $options = array(
+ 'guid' => $this->guid,
+ 'limit' => 0
+ );
+ if ($name) {
+ $options['metadata_name'] = $name;
+ }
+
+ return elgg_enable_metadata($options);
+ }
/**
* Get a piece of volatile (non-persisted) data on this entity.
@@ -363,7 +516,6 @@ abstract class ElggEntity extends ElggData implements
}
}
-
/**
* Set a piece of volatile (non-persisted) data on this entity
*
@@ -380,7 +532,6 @@ abstract class ElggEntity extends ElggData implements
$this->volatile[$name] = $value;
}
-
/**
* Remove all relationships to and from this entity.
*
@@ -389,13 +540,26 @@ abstract class ElggEntity extends ElggData implements
* @see ElggEntity::addRelationship()
* @see ElggEntity::removeRelationship()
*/
- public function clearRelationships() {
+ public function deleteRelationships() {
remove_entity_relationships($this->getGUID());
remove_entity_relationships($this->getGUID(), "", true);
return true;
}
/**
+ * Remove all relationships to and from this entity.
+ *
+ * @return bool
+ * @see ElggEntity::addRelationship()
+ * @see ElggEntity::removeRelationship()
+ * @deprecated 1.8 Use ->deleteRelationship()
+ */
+ public function clearRelationships() {
+ elgg_deprecated_notice('ElggEntity->clearRelationships() is deprecated by ->deleteRelationships()', 1.8);
+ return $this->deleteRelationships();
+ }
+
+ /**
* Add a relationship between this an another entity.
*
* @tip Read the relationship like "$guid is a $relationship of this entity."
@@ -435,10 +599,14 @@ abstract class ElggEntity extends ElggData implements
* @param mixed $value Value of private setting
*
* @return bool
- * @link http://docs.elgg.org/DataModel/Entities/PrivateSettings
*/
function setPrivateSetting($name, $value) {
- return set_private_setting($this->getGUID(), $name, $value);
+ if ((int) $this->guid > 0) {
+ return set_private_setting($this->getGUID(), $name, $value);
+ } else {
+ $this->temp_private_settings[$name] = $value;
+ return true;
+ }
}
/**
@@ -449,7 +617,14 @@ abstract class ElggEntity extends ElggData implements
* @return mixed
*/
function getPrivateSetting($name) {
- return get_private_setting($this->getGUID(), $name);
+ if ((int) ($this->guid) > 0) {
+ return get_private_setting($this->getGUID(), $name);
+ } else {
+ if (isset($this->temp_private_settings[$name])) {
+ return $this->temp_private_settings[$name];
+ }
+ }
+ return null;
}
/**
@@ -464,10 +639,117 @@ abstract class ElggEntity extends ElggData implements
}
/**
+ * Deletes all annotations on this object (annotations.entity_guid = $this->guid).
+ * If you pass a name, only annotations matching that name will be deleted.
+ *
+ * @warning Calling this with no or empty arguments will clear all annotations on the entity.
+ *
+ * @param null|string $name The annotations name to remove.
+ * @return bool
+ * @since 1.8
+ */
+ public function deleteAnnotations($name = null) {
+ $options = array(
+ 'guid' => $this->guid,
+ 'limit' => 0
+ );
+ if ($name) {
+ $options['annotation_name'] = $name;
+ }
+
+ return elgg_delete_annotations($options);
+ }
+
+ /**
+ * Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
+ * If you pass a name, only annotations matching that name will be deleted.
+ *
+ * @param null|string $name The name of annotations to delete.
+ * @return bool
+ * @since 1.8
+ */
+ public function deleteOwnedAnnotations($name = null) {
+ // access is turned off for this because they might
+ // no longer have access to an entity they created annotations on.
+ $ia = elgg_set_ignore_access(true);
+ $options = array(
+ 'annotation_owner_guid' => $this->guid,
+ 'limit' => 0
+ );
+ if ($name) {
+ $options['annotation_name'] = $name;
+ }
+
+ $r = elgg_delete_annotations($options);
+ elgg_set_ignore_access($ia);
+ return $r;
+ }
+
+ /**
+ * Disables annotations for this entity, optionally based on name.
+ *
+ * @param string $name An options name of annotations to disable.
+ * @return bool
+ * @since 1.8
+ */
+ public function disableAnnotations($name = '') {
+ $options = array(
+ 'guid' => $this->guid,
+ 'limit' => 0
+ );
+ if ($name) {
+ $options['annotation_name'] = $name;
+ }
+
+ return elgg_disable_annotations($options);
+ }
+
+ /**
+ * Enables annotations for this entity, optionally based on name.
+ *
+ * @warning Before calling this, you must use {@link access_show_hidden_entities()}
+ *
+ * @param string $name An options name of annotations to enable.
+ * @return bool
+ * @since 1.8
+ */
+ public function enableAnnotations($name = '') {
+ $options = array(
+ 'guid' => $this->guid,
+ 'limit' => 0
+ );
+ if ($name) {
+ $options['annotation_name'] = $name;
+ }
+
+ return elgg_enable_annotations($options);
+ }
+
+ /**
+ * Helper function to return annotation calculation results
+ *
+ * @param string $name The annotation name.
+ * @param string $calculation A valid MySQL function to run its values through
+ * @return mixed
+ */
+ private function getAnnotationCalculation($name, $calculation) {
+ $options = array(
+ 'guid' => $this->getGUID(),
+ 'annotation_name' => $name,
+ 'annotation_calculation' => $calculation
+ );
+
+ return elgg_get_annotations($options);
+ }
+
+ /**
* Adds an annotation to an entity.
*
* @warning By default, annotations are private.
*
+ * @warning Annotating an unsaved entity more than once with the same name
+ * will only save the last annotation.
+ *
* @param string $name Annotation name
* @param mixed $value Annotation value
* @param int $access_id Access ID
@@ -475,8 +757,6 @@ abstract class ElggEntity extends ElggData implements
* @param string $vartype The type of annotation value
*
* @return bool
- *
- * @link http://docs.elgg.org/DataModel/Annotations
*/
function annotate($name, $value, $access_id = ACCESS_PRIVATE, $owner_id = 0, $vartype = "") {
if ((int) $this->guid > 0) {
@@ -493,15 +773,29 @@ abstract class ElggEntity extends ElggData implements
* @param string $name Annotation name
* @param int $limit Limit
* @param int $offset Offset
- * @param string $order asc or desc
+ * @param string $order Order by time: asc or desc
*
* @return array
*/
function getAnnotations($name, $limit = 50, $offset = 0, $order = "asc") {
if ((int) ($this->guid) > 0) {
- return get_annotations($this->getGUID(), "", "", $name, "", 0, $limit, $offset, $order);
+
+ $options = array(
+ 'guid' => $this->guid,
+ 'annotation_name' => $name,
+ 'limit' => $limit,
+ 'offset' => $offset,
+ );
+
+ if ($order != 'asc') {
+ $options['reverse_order_by'] = true;
+ }
+
+ return elgg_get_annotations($options);
+ } else if (isset($this->temp_annotations[$name])) {
+ return array($this->temp_annotations[$name]);
} else {
- return $this->temp_annotations[$name];
+ return array();
}
}
@@ -512,11 +806,12 @@ abstract class ElggEntity extends ElggData implements
* all annotations on the entity.
*
* @param string $name Annotation name
- *
* @return bool
+ * @deprecated 1.8 Use ->deleteAnnotations()
*/
function clearAnnotations($name = "") {
- return clear_annotations($this->getGUID(), $name);
+ elgg_deprecated_notice('ElggEntity->clearAnnotations() is deprecated by ->deleteAnnotations()', 1.8);
+ return $this->deleteAnnotations($name);
}
/**
@@ -527,7 +822,7 @@ abstract class ElggEntity extends ElggData implements
* @return int
*/
function countAnnotations($name = "") {
- return count_annotations($this->getGUID(), "", "", $name);
+ return $this->getAnnotationCalculation($name, 'count');
}
/**
@@ -538,7 +833,7 @@ abstract class ElggEntity extends ElggData implements
* @return int
*/
function getAnnotationsAvg($name) {
- return get_annotations_avg($this->getGUID(), "", "", $name);
+ return $this->getAnnotationCalculation($name, 'avg');
}
/**
@@ -549,7 +844,7 @@ abstract class ElggEntity extends ElggData implements
* @return int
*/
function getAnnotationsSum($name) {
- return get_annotations_sum($this->getGUID(), "", "", $name);
+ return $this->getAnnotationCalculation($name, 'sum');
}
/**
@@ -560,7 +855,7 @@ abstract class ElggEntity extends ElggData implements
* @return int
*/
function getAnnotationsMin($name) {
- return get_annotations_min($this->getGUID(), "", "", $name);
+ return $this->getAnnotationCalculation($name, 'min');
}
/**
@@ -571,7 +866,24 @@ abstract class ElggEntity extends ElggData implements
* @return int
*/
function getAnnotationsMax($name) {
- return get_annotations_max($this->getGUID(), "", "", $name);
+ return $this->getAnnotationCalculation($name, 'max');
+ }
+
+ /**
+ * Count the number of comments attached to this entity.
+ *
+ * @return int Number of comments
+ * @since 1.8.0
+ */
+ function countComments() {
+ $params = array('entity' => $this);
+ $num = elgg_trigger_plugin_hook('comments:count', $this->getType(), $params);
+
+ if (is_int($num)) {
+ return $num;
+ } else {
+ return $this->getAnnotationCalculation('generic_comment', 'count');
+ }
}
/**
@@ -628,18 +940,18 @@ abstract class ElggEntity extends ElggData implements
* @param ElggMetadata $metadata The piece of metadata to specifically check
* @param int $user_guid The user GUID, optionally (default: logged in user)
*
- * @return true|false
+ * @return bool
*/
function canEditMetadata($metadata = null, $user_guid = 0) {
return can_edit_entity_metadata($this->getGUID(), $user_guid, $metadata);
}
/**
- * Can a user write to this entity
+ * 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
+ * @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
*/
@@ -648,6 +960,61 @@ abstract class ElggEntity extends ElggData implements
}
/**
+ * 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
@@ -659,7 +1026,7 @@ abstract class ElggEntity extends ElggData implements
/**
* Returns the guid.
*
- * @return int GUID
+ * @return int|null GUID
*/
public function getGUID() {
return $this->get('guid');
@@ -815,18 +1182,49 @@ abstract class ElggEntity extends ElggData implements
}
/**
+ * Get the URL for this entity's icon
+ *
+ * Plugins can register for the 'entity:icon:url', <type> plugin hook
+ * to customize the icon for an entity.
+ *
+ * @param string $size Size of the icon: tiny, small, medium, large
+ *
+ * @return string The URL
+ * @since 1.8.0
+ */
+ public function getIconURL($size = 'medium') {
+ $size = elgg_strtolower($size);
+
+ if (isset($this->icon_override[$size])) {
+ elgg_deprecated_notice("icon_override on an individual entity is deprecated", 1.8);
+ return $this->icon_override[$size];
+ }
+
+ $type = $this->getType();
+ $params = array(
+ 'entity' => $this,
+ 'size' => $size,
+ );
+
+ $url = elgg_trigger_plugin_hook('entity:icon:url', $type, $params, null);
+ if ($url == null) {
+ $url = "_graphics/icons/default/$size.png";
+ }
+
+ return elgg_normalize_url($url);
+ }
+
+ /**
* Returns a URL for the entity's icon.
*
* @param string $size Either 'large', 'medium', 'small' or 'tiny'
*
* @return string The url or false if no url could be worked out.
- * @see get_entity_icon_url()
+ * @deprecated Use getIconURL()
*/
public function getIcon($size = 'medium') {
- if (isset($this->icon_override[$size])) {
- return $this->icon_override[$size];
- }
- return get_entity_icon_url($this, $size);
+ elgg_deprecated_notice("getIcon() deprecated by getIconURL()", 1.8);
+ return $this->getIconURL($size);
}
/**
@@ -838,8 +1236,11 @@ abstract class ElggEntity extends ElggData implements
* @param string $size The size its for.
*
* @return bool
+ * @deprecated 1.8 See getIconURL() for the plugin hook to use
*/
public function setIcon($url, $size = 'medium') {
+ elgg_deprecated_notice("icon_override on an individual entity is deprecated", 1.8);
+
$url = sanitise_string($url);
$size = sanitise_string($size);
@@ -861,22 +1262,31 @@ abstract class ElggEntity extends ElggData implements
}
/**
- * Save attributes to the entities table.
+ * Save an entity.
*
- * @return bool
+ * @return bool|int
* @throws IOException
*/
public function save() {
- $guid = (int) $this->guid;
+ $guid = $this->getGUID();
if ($guid > 0) {
- cache_entity($this);
- return update_entity(
- $this->get('guid'),
+ // See #5600. This ensures the lower level can_edit_entity() check will use a
+ // fresh entity from the DB so it sees the persisted owner_guid
+ _elgg_disable_caching_for_entity($guid);
+
+ $ret = update_entity(
+ $guid,
$this->get('owner_guid'),
$this->get('access_id'),
- $this->get('container_guid')
+ $this->get('container_guid'),
+ $this->get('time_created')
);
+
+ _elgg_enable_caching_for_entity($guid);
+ _elgg_cache_entity($this);
+
+ return $ret;
} else {
// Create a new entity (nb: using attribute array directly
// 'cos set function does something special!)
@@ -898,8 +1308,7 @@ abstract class ElggEntity extends ElggData implements
}
}
- // Save any unsaved annotations metadata.
- // @todo How to capture extra information (access id etc)
+ // Save any unsaved annotations.
if (sizeof($this->temp_annotations) > 0) {
foreach ($this->temp_annotations as $name => $value) {
$this->annotate($name, $value);
@@ -907,14 +1316,19 @@ abstract class ElggEntity extends ElggData implements
}
}
+ // Save any unsaved private settings.
+ if (sizeof($this->temp_private_settings) > 0) {
+ foreach ($this->temp_private_settings as $name => $value) {
+ $this->setPrivateSetting($name, $value);
+ unset($this->temp_private_settings[$name]);
+ }
+ }
+
// set the subtype to id now rather than a string
$this->attributes['subtype'] = get_subtype_id($this->attributes['type'],
$this->attributes['subtype']);
- // Cache object handle
- if ($this->attributes['guid']) {
- cache_entity($this);
- }
+ _elgg_cache_entity($this);
return $this->attributes['guid'];
}
@@ -923,12 +1337,16 @@ abstract class ElggEntity extends ElggData implements
/**
* Loads attributes from the entities table into the object.
*
- * @param int $guid GUID of Entity
+ * @param mixed $guid GUID of entity or stdClass object from entities table
*
* @return bool
*/
protected function load($guid) {
- $row = get_entity_as_row($guid);
+ if ($guid instanceof stdClass) {
+ $row = $guid;
+ } else {
+ $row = get_entity_as_row($guid);
+ }
if ($row) {
// Create the array if necessary - all subclasses should test before creating
@@ -947,9 +1365,12 @@ abstract class ElggEntity extends ElggData implements
$this->attributes['tables_loaded']++;
}
+ // guid needs to be an int https://github.com/elgg/elgg/issues/4111
+ $this->attributes['guid'] = (int)$this->attributes['guid'];
+
// Cache object handle
if ($this->attributes['guid']) {
- cache_entity($this);
+ _elgg_cache_entity($this);
}
return true;
@@ -1046,13 +1467,10 @@ abstract class ElggEntity extends ElggData implements
*
* @param string $location String representation of the location
*
- * @return true
+ * @return bool
*/
public function setLocation($location) {
- $location = sanitise_string($location);
-
$this->location = $location;
-
return true;
}
@@ -1062,13 +1480,10 @@ abstract class ElggEntity extends ElggData implements
* @param float $lat Latitude
* @param float $long Longitude
*
- * @return true
+ * @return bool
* @todo Unimplemented
*/
public function setLatLong($lat, $long) {
- $lat = sanitise_string($lat);
- $long = sanitise_string($long);
-
$this->set('geo:lat', $lat);
$this->set('geo:long', $long);
@@ -1078,20 +1493,20 @@ abstract class ElggEntity extends ElggData implements
/**
* Return the entity's latitude.
*
- * @return int
+ * @return float
* @todo Unimplemented
*/
public function getLatitude() {
- return $this->get('geo:lat');
+ return (float)$this->get('geo:lat');
}
/**
* Return the entity's longitude
*
- * @return Int
+ * @return float
*/
public function getLongitude() {
- return $this->get('geo:long');
+ return (float)$this->get('geo:long');
}
/*
@@ -1198,36 +1613,36 @@ abstract class ElggEntity extends ElggData implements
foreach ($this->attributes as $k => $v) {
$meta = NULL;
- if (in_array( $k, $exportable_values)) {
+ if (in_array($k, $exportable_values)) {
switch ($k) {
- case 'guid' : // Dont use guid in OpenDD
- case 'type' : // Type and subtype already taken care of
- case 'subtype' :
- break;
+ case 'guid': // Dont use guid in OpenDD
+ case 'type': // Type and subtype already taken care of
+ case 'subtype':
+ break;
- case 'time_created' : // Created = published
+ case 'time_created': // Created = published
$odd->setAttribute('published', date("r", $v));
- break;
+ break;
- case 'site_guid' : // Container
+ case 'site_guid': // Container
$k = 'site_uuid';
$v = guid_to_uuid($v);
$meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
- break;
+ break;
- case 'container_guid' : // Container
+ case 'container_guid': // Container
$k = 'container_uuid';
$v = guid_to_uuid($v);
$meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
- break;
+ break;
- case 'owner_guid' : // Convert owner guid to uuid, this will be stored in metadata
+ case 'owner_guid': // Convert owner guid to uuid, this will be stored in metadata
$k = 'owner_uuid';
$v = guid_to_uuid($v);
$meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
- break;
+ break;
- default :
+ default:
$meta = new ODDMetaData($uuid . "attr/$k/", $uuid, $k, $v);
}
@@ -1245,7 +1660,7 @@ abstract class ElggEntity extends ElggData implements
*/
elgg_set_viewtype('default');
- $view = elgg_view_entity($this, true);
+ $view = elgg_view_entity($this, array('full_view' => true));
elgg_set_viewtype();
$tmp[] = new ODDMetaData($uuid . "volatile/renderedentity/", $uuid,
@@ -1261,9 +1676,11 @@ abstract class ElggEntity extends ElggData implements
/**
* Import data from an parsed ODD xml data array.
*
- * @param array $data XML data
+ * @param ODD $data XML data
*
* @return true
+ *
+ * @throws InvalidParameterException
*/
public function import(ODD $data) {
if (!($data instanceof ODDEntity)) {
@@ -1275,7 +1692,7 @@ abstract class ElggEntity extends ElggData implements
$this->attributes['subtype'] = $data->getAttribute('subclass');
// Set owner
- $this->attributes['owner_guid'] = get_loggedin_userid(); // Import as belonging to importer.
+ $this->attributes['owner_guid'] = elgg_get_logged_in_user_guid(); // Import as belonging to importer.
// Set time
$this->attributes['time_created'] = strtotime($data->getAttribute('published'));
@@ -1325,8 +1742,6 @@ abstract class ElggEntity extends ElggData implements
* @return array
*/
public function getTags($tag_names = NULL) {
- global $CONFIG;
-
if ($tag_names && !is_array($tag_names)) {
$tag_names = array($tag_names);
}
@@ -1352,4 +1767,4 @@ abstract class ElggEntity extends ElggData implements
return $entity_tags;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggExtender.php b/engine/classes/ElggExtender.php
index 6ab9ff3ce..25aba354f 100644
--- a/engine/classes/ElggExtender.php
+++ b/engine/classes/ElggExtender.php
@@ -3,8 +3,7 @@
* The base class for ElggEntity extenders.
*
* Extenders allow you to attach extended information to an
- * ElggEntity. Core supports two: ElggAnnotation, ElggMetadata,
- * and ElggRelationship
+ * ElggEntity. Core supports two: ElggAnnotation and ElggMetadata.
*
* Saving the extender data to database is handled by the child class.
*
@@ -16,9 +15,24 @@
* @link http://docs.elgg.org/DataModel/Extenders
* @see ElggAnnotation
* @see ElggMetadata
+ *
+ * @property string $type annotation or metadata (read-only after save)
+ * @property int $id The unique identifier (read-only)
+ * @property int $entity_guid The GUID of the entity that this extender describes
+ * @property int $access_id Specifies the visibility level of this extender
+ * @property string $name The name of this extender
+ * @property mixed $value The value of the extender (int or string)
+ * @property int $time_created A UNIX timestamp of when the extender was created (read-only, set on first save)
*/
-abstract class ElggExtender extends ElggData
-{
+abstract class ElggExtender extends ElggData {
+
+ /**
+ * (non-PHPdoc)
+ *
+ * @see ElggData::initializeAttributes()
+ *
+ * @return void
+ */
protected function initializeAttributes() {
parent::initializeAttributes();
@@ -33,7 +47,7 @@ abstract class ElggExtender extends ElggData
* @return mixed
*/
protected function get($name) {
- if (isset($this->attributes[$name])) {
+ if (array_key_exists($name, $this->attributes)) {
// Sanitise value if necessary
if ($name == 'value') {
switch ($this->attributes['value_type']) {
@@ -157,7 +171,7 @@ abstract class ElggExtender extends ElggData
public function export() {
$uuid = get_uuid_from_object($this);
- $meta = new ODDMetadata($uuid, guid_to_uuid($this->entity_guid), $this->attributes['name'],
+ $meta = new ODDMetaData($uuid, guid_to_uuid($this->entity_guid), $this->attributes['name'],
$this->attributes['value'], $this->attributes['type'], guid_to_uuid($this->owner_guid));
$meta->setAttribute('published', date("r", $this->time_created));
diff --git a/engine/classes/ElggFile.php b/engine/classes/ElggFile.php
index 2a0d6fb28..23080834b 100644
--- a/engine/classes/ElggFile.php
+++ b/engine/classes/ElggFile.php
@@ -93,6 +93,7 @@ class ElggFile extends ElggObject {
$container_guid = $this->container_guid;
}
$fs = $this->getFilestore();
+ // @todo add getSize() to ElggFilestore
return $fs->getSize($prefix, $container_guid);
}
@@ -121,6 +122,49 @@ class ElggFile extends ElggObject {
}
/**
+ * Detects mime types based on filename or actual file.
+ *
+ * @param mixed $file The full path of the file to check. For uploaded files, use tmp_name.
+ * @param mixed $default A default. Useful to pass what the browser thinks it is.
+ * @since 1.7.12
+ *
+ * @note If $file is provided, this may be called statically
+ *
+ * @return mixed Detected type on success, false on failure.
+ */
+ public function detectMimeType($file = null, $default = null) {
+ if (!$file) {
+ if (isset($this) && $this->filename) {
+ $file = $this->filename;
+ } else {
+ return false;
+ }
+ }
+
+ $mime = false;
+
+ // for PHP5 folks.
+ if (function_exists('finfo_file') && defined('FILEINFO_MIME_TYPE')) {
+ $resource = finfo_open(FILEINFO_MIME_TYPE);
+ if ($resource) {
+ $mime = finfo_file($resource, $file);
+ }
+ }
+
+ // for everyone else.
+ if (!$mime && function_exists('mime_content_type')) {
+ $mime = mime_content_type($file);
+ }
+
+ // default
+ if (!$mime) {
+ return $default;
+ }
+
+ return $mime;
+ }
+
+ /**
* Set the optional file description.
*
* @param string $description The description.
@@ -137,6 +181,8 @@ class ElggFile extends ElggObject {
* @param string $mode Either read/write/append
*
* @return resource File handler
+ *
+ * @throws IOException|InvalidParameterException
*/
public function open($mode) {
if (!$this->getFilename()) {
@@ -229,9 +275,14 @@ class ElggFile extends ElggObject {
*/
public function delete() {
$fs = $this->getFilestore();
- if ($fs->delete($this)) {
- return parent::delete();
+
+ $result = $fs->delete($this);
+
+ if ($this->getGUID() && $result) {
+ $result = parent::delete();
}
+
+ return $result;
}
/**
@@ -244,6 +295,7 @@ class ElggFile extends ElggObject {
public function seek($position) {
$fs = $this->getFilestore();
+ // @todo add seek() to ElggFilestore
return $fs->seek($this->handle, $position);
}
@@ -302,10 +354,12 @@ class ElggFile extends ElggObject {
/**
* Return a filestore suitable for saving this file.
- * This filestore is either a pre-registered filestore, a filestore loaded from metatags saved
- * along side this file, or the system default.
+ * This filestore is either a pre-registered filestore,
+ * a filestore as recorded in metadata or the system default.
*
* @return ElggFilestore
+ *
+ * @throws ClassNotFoundException
*/
protected function getFilestore() {
// Short circuit if already set.
@@ -313,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'])) {
+ // 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($parameters['filestore'],
- $this->guid));
+ array($filestore, $this->guid));
throw new ClassNotFoundException($msg);
}
- // Create new filestore object
- $this->filestore = new $parameters['filestore']();
-
+ $this->filestore = new $filestore();
$this->filestore->setParameters($parameters);
- } else {
- // @todo - should we log error if filestore not set
+ // @todo explain why $parameters will always be set here (PhpStorm complains)
}
-
- // if still nothing then set filestore to default
+ // this means the entity hasn't been saved so fallback to default
if (!$this->filestore) {
$this->filestore = get_default_filestore();
}
diff --git a/engine/classes/ElggFileCache.php b/engine/classes/ElggFileCache.php
index c27e9aa32..94143f777 100644
--- a/engine/classes/ElggFileCache.php
+++ b/engine/classes/ElggFileCache.php
@@ -13,6 +13,8 @@ class ElggFileCache extends ElggCache {
* @param string $cache_path The cache path.
* @param int $max_age Maximum age in seconds, 0 if no limit.
* @param int $max_size Maximum size of cache in seconds, 0 if no limit.
+ *
+ * @throws ConfigurationException
*/
function __construct($cache_path, $max_age = 0, $max_size = 0) {
$this->setVariable("cache_path", $cache_path);
@@ -24,6 +26,7 @@ class ElggFileCache extends ElggCache {
}
}
+ // @codingStandardsIgnoreStart
/**
* Create and return a handle to a file.
*
@@ -39,6 +42,7 @@ class ElggFileCache extends ElggCache {
return $this->createFile($filename, $rw);
}
+ // @codingStandardsIgnoreEnd
/**
* Create and return a handle to a file.
@@ -70,6 +74,7 @@ class ElggFileCache extends ElggCache {
return fopen($path . $filename, $rw);
}
+ // @codingStandardsIgnoreStart
/**
* Create a sanitised filename for the file.
*
@@ -84,6 +89,7 @@ class ElggFileCache extends ElggCache {
return $filename;
}
+ // @codingStandardsIgnoreEnd
/**
* Create a sanitised filename for the file.
@@ -161,12 +167,25 @@ class ElggFileCache extends ElggCache {
}
/**
- * This was probably meant to delete everything?
+ * Delete all files in the directory of this file cache
*
* @return void
*/
public function clear() {
- // @todo writeme
+ $dir = $this->getVariable("cache_path");
+
+ $exclude = array(".", "..");
+
+ $files = scandir($dir);
+ if (!$files) {
+ return;
+ }
+
+ foreach ($files as $f) {
+ if (!in_array($f, $exclude)) {
+ unlink($dir . $f);
+ }
+ }
}
/**
@@ -184,7 +203,7 @@ class ElggFileCache extends ElggCache {
return;
}
- $exclude = array(".","..");
+ $exclude = array(".", "..");
$files = scandir($dir);
if (!$files) {
@@ -208,4 +227,4 @@ class ElggFileCache extends ElggCache {
}
}
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggFilestore.php b/engine/classes/ElggFilestore.php
index 61ce167d0..16430feac 100644
--- a/engine/classes/ElggFilestore.php
+++ b/engine/classes/ElggFilestore.php
@@ -136,4 +136,4 @@ abstract class ElggFilestore {
* @return bool
*/
abstract public function exists(ElggFile $file);
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggGroup.php b/engine/classes/ElggGroup.php
index 81c1db138..7e69b7a84 100644
--- a/engine/classes/ElggGroup.php
+++ b/engine/classes/ElggGroup.php
@@ -5,6 +5,9 @@
*
* @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 {
@@ -13,8 +16,6 @@ class ElggGroup extends ElggEntity
* Sets the type to group.
*
* @return void
- *
- * @deprecated 1.8 Use initializeAttributes
*/
protected function initializeAttributes() {
parent::initializeAttributes();
@@ -26,12 +27,12 @@ class ElggGroup extends ElggEntity
}
/**
- * Construct a new user entity, optionally from a given id value.
+ * Construct a new group entity, optionally from a given guid value.
*
* @param mixed $guid If an int, load that GUID.
- * If a db row then will attempt to load the rest of the data.
+ * If an entity table db row, then will load the rest of the data.
*
- * @throws Exception if there was a problem creating the user.
+ * @throws IOException|InvalidParameterException if there was a problem creating the group.
*/
function __construct($guid = null) {
$this->initializeAttributes();
@@ -40,28 +41,25 @@ class ElggGroup extends ElggEntity
$this->initialise_attributes(false);
if (!empty($guid)) {
- // Is $guid is a DB row - either a entity row, or a user table row.
+ // Is $guid is a entity table DB row
if ($guid instanceof stdClass) {
// Load the rest
- if (!$this->load($guid->guid)) {
+ if (!$this->load($guid)) {
$msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid));
throw new IOException($msg);
}
-
- // Is $guid is an ElggGroup? Use a copy constructor
} else if ($guid instanceof ElggGroup) {
+ // $guid is an ElggGroup so this is a copy constructor
elgg_deprecated_notice('This type of usage of the ElggGroup constructor was deprecated. Please use the clone method.', 1.7);
foreach ($guid->attributes as $key => $value) {
$this->attributes[$key] = $value;
}
-
- // Is this is an ElggEntity but not an ElggGroup = ERROR!
} else if ($guid instanceof ElggEntity) {
+ // @todo why separate from else
throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggGroup'));
-
- // We assume if we have got this far, $guid is an int
} else if (is_numeric($guid)) {
+ // $guid is a GUID so load entity
if (!$this->load($guid)) {
throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid)));
}
@@ -219,6 +217,7 @@ class ElggGroup extends ElggEntity
* @return array|false
*/
public function getObjects($subtype = "", $limit = 10, $offset = 0) {
+ // @todo are we deprecating this method, too?
return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", $limit, $offset, false);
}
@@ -232,6 +231,7 @@ class ElggGroup extends ElggEntity
* @return array|false
*/
public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0) {
+ // @todo are we deprecating this method, too?
return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", $limit, $offset, false);
}
@@ -243,6 +243,7 @@ class ElggGroup extends ElggEntity
* @return array|false
*/
public function countObjects($subtype = "") {
+ // @todo are we deprecating this method, too?
return get_objects_in_group($this->getGUID(), $subtype, 0, 0, "", 10, 0, true);
}
@@ -283,9 +284,9 @@ class ElggGroup extends ElggEntity
*
* @return bool
*/
- public function isMember($user = 0) {
+ public function isMember($user = null) {
if (!($user instanceof ElggUser)) {
- $user = get_loggedin_user();
+ $user = elgg_get_logged_in_user_entity();
}
if (!($user instanceof ElggUser)) {
return false;
@@ -309,45 +310,32 @@ class ElggGroup extends ElggEntity
*
* @param ElggUser $user User
*
- * @return void
+ * @return bool
*/
public function leave(ElggUser $user) {
return leave_group($this->getGUID(), $user->getGUID());
}
/**
- * Override the load function.
- * This function will ensure that all data is loaded (were possible), so
- * if only part of the ElggGroup is loaded, it'll load the rest.
+ * Load the ElggGroup data from the database
*
- * @param int $guid GUID of an ElggGroup entity
+ * @param mixed $guid GUID of an ElggGroup entity or database row from entity table
*
- * @return true
+ * @return bool
*/
protected function load($guid) {
- // Test to see if we have the generic stuff
- if (!parent::load($guid)) {
- return false;
- }
+ $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';
- // Check the type
- if ($this->attributes['type'] != 'group') {
- $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class()));
- throw new InvalidClassException($msg);
+ $attrs = $attr_loader->getRequiredAttributes($guid);
+ if (!$attrs) {
+ return false;
}
- // Load missing data
- $row = get_group_entity_as_row($guid);
- if (($row) && (!$this->isFullyLoaded())) {
- // If $row isn't a cached copy then increment the counter
- $this->attributes['tables_loaded'] ++;
- }
-
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach ($objarray as $key => $value) {
- $this->attributes[$key] = $value;
- }
+ $this->attributes = $attrs;
+ $this->attributes['tables_loaded'] = 2;
+ _elgg_cache_entity($this);
return true;
}
@@ -364,7 +352,12 @@ class ElggGroup extends ElggEntity
}
// Now save specific stuff
- return create_group_entity($this->get('guid'), $this->get('name'), $this->get('description'));
+
+ _elgg_disable_caching_for_entity($this->guid);
+ $ret = create_group_entity($this->get('guid'), $this->get('name'), $this->get('description'));
+ _elgg_enable_caching_for_entity($this->guid);
+
+ return $ret;
}
// EXPORTABLE INTERFACE ////////////////////////////////////////////////////////////
@@ -380,4 +373,21 @@ class ElggGroup extends ElggEntity
'description',
));
}
-} \ No newline at end of file
+
+ /**
+ * Can a user comment on this group?
+ *
+ * @see ElggEntity::canComment()
+ *
+ * @param int $user_guid User guid (default is logged in user)
+ * @return bool
+ * @since 1.8.0
+ */
+ public function canComment($user_guid = 0) {
+ $result = parent::canComment($user_guid);
+ if ($result !== null) {
+ return $result;
+ }
+ return false;
+ }
+}
diff --git a/engine/classes/ElggGroupItemVisibility.php b/engine/classes/ElggGroupItemVisibility.php
new file mode 100644
index 000000000..2c7e2abb4
--- /dev/null
+++ b/engine/classes/ElggGroupItemVisibility.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * Determines if otherwise visible items should be hidden from a user due to group
+ * policy or visibility.
+ *
+ * @class ElggGroupItemVisibility
+ * @package Elgg.Core
+ * @subpackage Groups
+ *
+ * @access private
+ */
+class ElggGroupItemVisibility {
+
+ const REASON_MEMBERSHIP = 'membershiprequired';
+ const REASON_LOGGEDOUT = 'loggedinrequired';
+ const REASON_NOACCESS = 'noaccess';
+
+ /**
+ * @var bool
+ */
+ public $shouldHideItems = false;
+
+ /**
+ * @var string
+ */
+ public $reasonHidden = '';
+
+ /**
+ * Determine visibility of items within a container for the current user
+ *
+ * @param int $container_guid GUID of a container (may/may not be a group)
+ *
+ * @return ElggGroupItemVisibility
+ *
+ * @todo Make this faster, considering it must run for every river item.
+ */
+ static public function factory($container_guid) {
+ // cache because this may be called repeatedly during river display, and
+ // due to need to check group visibility, cache will be disabled for some
+ // get_entity() calls
+ static $cache = array();
+
+ $ret = new ElggGroupItemVisibility();
+
+ if (!$container_guid) {
+ return $ret;
+ }
+
+ $user = elgg_get_logged_in_user_entity();
+ $user_guid = $user ? $user->guid : 0;
+
+ $container_guid = (int) $container_guid;
+
+ $cache_key = "$container_guid|$user_guid";
+ if (empty($cache[$cache_key])) {
+ // compute
+
+ $container = get_entity($container_guid);
+ $is_visible = (bool) $container;
+
+ if (!$is_visible) {
+ // see if it *really* exists...
+ $prev_access = elgg_set_ignore_access();
+ $container = get_entity($container_guid);
+ elgg_set_ignore_access($prev_access);
+ }
+
+ if ($container && $container instanceof ElggGroup) {
+ /* @var ElggGroup $container */
+
+ if ($is_visible) {
+ if (!$container->isPublicMembership()) {
+ if ($user) {
+ if (!$container->isMember($user) && !$user->isAdmin()) {
+ $ret->shouldHideItems = true;
+ $ret->reasonHidden = self::REASON_MEMBERSHIP;
+ }
+ } else {
+ $ret->shouldHideItems = true;
+ $ret->reasonHidden = self::REASON_LOGGEDOUT;
+ }
+ }
+ } else {
+ $ret->shouldHideItems = true;
+ $ret->reasonHidden = self::REASON_NOACCESS;
+ }
+ }
+ $cache[$cache_key] = $ret;
+ }
+ return $cache[$cache_key];
+ }
+}
diff --git a/engine/classes/ElggHMACCache.php b/engine/classes/ElggHMACCache.php
index 8d994d013..c2f468815 100644
--- a/engine/classes/ElggHMACCache.php
+++ b/engine/classes/ElggHMACCache.php
@@ -96,4 +96,4 @@ class ElggHMACCache extends ElggCache {
delete_data("DELETE from {$CONFIG->dbprefix}hmac_cache where ts<$expires");
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggLRUCache.php b/engine/classes/ElggLRUCache.php
new file mode 100644
index 000000000..f51af2ed7
--- /dev/null
+++ b/engine/classes/ElggLRUCache.php
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * Least Recently Used Cache
+ *
+ * A fixed sized cache that removes the element used last when it reaches its
+ * size limit.
+ *
+ * Based on https://github.com/cash/LRUCache
+ *
+ * @access private
+ *
+ * @package Elgg.Core
+ * @subpackage Cache
+ */
+class ElggLRUCache implements ArrayAccess {
+ /** @var int */
+ protected $maximumSize;
+
+ /**
+ * The front of the array contains the LRU element
+ *
+ * @var array
+ */
+ protected $data = array();
+
+ /**
+ * Create a LRU Cache
+ *
+ * @param int $size The size of the cache
+ * @throws InvalidArgumentException
+ */
+ public function __construct($size) {
+ if (!is_int($size) || $size <= 0) {
+ throw new InvalidArgumentException();
+ }
+ $this->maximumSize = $size;
+ }
+
+ /**
+ * Get the value cached with this key
+ *
+ * @param int|string $key The key. Strings that are ints are cast to ints.
+ * @param mixed $default The value to be returned if key not found. (Optional)
+ * @return mixed
+ */
+ public function get($key, $default = null) {
+ if (isset($this->data[$key])) {
+ $this->recordAccess($key);
+ return $this->data[$key];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * Add something to the cache
+ *
+ * @param int|string $key The key. Strings that are ints are cast to ints.
+ * @param mixed $value The value to cache
+ * @return void
+ */
+ public function set($key, $value) {
+ if (isset($this->data[$key])) {
+ $this->data[$key] = $value;
+ $this->recordAccess($key);
+ } else {
+ $this->data[$key] = $value;
+ if ($this->size() > $this->maximumSize) {
+ // remove least recently used element (front of array)
+ reset($this->data);
+ unset($this->data[key($this->data)]);
+ }
+ }
+ }
+
+ /**
+ * Get the number of elements in the cache
+ *
+ * @return int
+ */
+ public function size() {
+ return count($this->data);
+ }
+
+ /**
+ * Does the cache contain an element with this key
+ *
+ * @param int|string $key The key
+ * @return boolean
+ */
+ public function containsKey($key) {
+ return isset($this->data[$key]);
+ }
+
+ /**
+ * Remove the element with this key.
+ *
+ * @param int|string $key The key
+ * @return mixed Value or null if not set
+ */
+ public function remove($key) {
+ if (isset($this->data[$key])) {
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ return $value;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Clear the cache
+ *
+ * @return void
+ */
+ public function clear() {
+ $this->data = array();
+ }
+
+ /**
+ * Moves the element from current position to end of array
+ *
+ * @param int|string $key The key
+ * @return void
+ */
+ protected function recordAccess($key) {
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+ }
+
+ /**
+ * Assigns a value for the specified key
+ *
+ * @see ArrayAccess::offsetSet()
+ *
+ * @param int|string $key The key to assign the value to.
+ * @param mixed $value The value to set.
+ * @return void
+ */
+ public function offsetSet($key, $value) {
+ $this->set($key, $value);
+ }
+
+ /**
+ * Get the value for specified key
+ *
+ * @see ArrayAccess::offsetGet()
+ *
+ * @param int|string $key The key to retrieve.
+ * @return mixed
+ */
+ public function offsetGet($key) {
+ return $this->get($key);
+ }
+
+ /**
+ * Unsets a key.
+ *
+ * @see ArrayAccess::offsetUnset()
+ *
+ * @param int|string $key The key to unset.
+ * @return void
+ */
+ public function offsetUnset($key) {
+ $this->remove($key);
+ }
+
+ /**
+ * Does key exist?
+ *
+ * @see ArrayAccess::offsetExists()
+ *
+ * @param int|string $key A key to check for.
+ * @return boolean
+ */
+ public function offsetExists($key) {
+ return $this->containsKey($key);
+ }
+}
diff --git a/engine/classes/ElggMemcache.php b/engine/classes/ElggMemcache.php
index 7d19fb2c7..91d50ab89 100644
--- a/engine/classes/ElggMemcache.php
+++ b/engine/classes/ElggMemcache.php
@@ -32,6 +32,8 @@ class ElggMemcache extends ElggSharedMemoryCache {
*
* @param string $namespace The namespace for this cache to write to -
* note, namespaces of the same name are shared!
+ *
+ * @throws ConfigurationException
*/
function __construct($namespace = 'default') {
global $CONFIG;
@@ -40,7 +42,7 @@ class ElggMemcache extends ElggSharedMemoryCache {
// Do we have memcache?
if (!class_exists('Memcache')) {
- throw new ConfigurationException(elgg_echo('memcache:notinstalled'));
+ throw new ConfigurationException('PHP memcache module not installed, you must install php5-memcache');
}
// Create memcache object
@@ -48,7 +50,7 @@ class ElggMemcache extends ElggSharedMemoryCache {
// Now add servers
if (!$CONFIG->memcache_servers) {
- throw new ConfigurationException(elgg_echo('memcache:noservers'));
+ throw new ConfigurationException('No memcache servers defined, please populate the $CONFIG->memcache_servers variable');
}
if (is_callable(array($this->memcache, 'addServer'))) {
@@ -85,7 +87,7 @@ class ElggMemcache extends ElggSharedMemoryCache {
// Get version
$this->version = $this->memcache->getVersion();
if (version_compare($this->version, ElggMemcache::$MINSERVERVERSION, '<')) {
- $msg = elgg_echo('memcache:versiontoolow',
+ $msg = vsprintf('Memcache needs at least version %s to run, you are running %s',
array(ElggMemcache::$MINSERVERVERSION,
$this->version
));
@@ -114,27 +116,11 @@ class ElggMemcache extends ElggSharedMemoryCache {
* Combine a key with the namespace.
* Memcache can only accept <250 char key. If the given key is too long it is shortened.
*
- * @deprecated 1.8 Use ElggMemcache::_makeMemcacheKey()
- *
* @param string $key The key
*
* @return string The new key.
*/
- private function make_memcache_key($key) {
- elgg_deprecated_notice('ElggMemcache::make_memcache_key() is deprecated by ::_makeMemcacheKey()', 1.8);
-
- return $this->_makeMemcacheKey($key);
- }
-
- /**
- * Combine a key with the namespace.
- * Memcache can only accept <250 char key. If the given key is too long it is shortened.
- *
- * @param string $key The key
- *
- * @return string The new key.
- */
- private function _makeMemcacheKey($key) {
+ private function makeMemcacheKey($key) {
$prefix = $this->getNamespace() . ":";
if (strlen($prefix . $key) > 250) {
@@ -147,16 +133,21 @@ class ElggMemcache extends ElggSharedMemoryCache {
/**
* Saves a name and value to the cache
*
- * @param string $key Name
- * @param string $data Value
+ * @param string $key Name
+ * @param string $data Value
+ * @param integer $expires Expires (in seconds)
*
* @return bool
*/
- public function save($key, $data) {
- $key = $this->_makeMemcacheKey($key);
+ public function save($key, $data, $expires = null) {
+ $key = $this->makeMemcacheKey($key);
+
+ if ($expires === null) {
+ $expires = $this->expires;
+ }
- $result = $this->memcache->set($key, $data, null, $this->expires);
- if (!$result) {
+ $result = $this->memcache->set($key, $data, null, $expires);
+ if ($result === false) {
elgg_log("MEMCACHE: FAILED TO SAVE $key", 'ERROR');
}
@@ -173,10 +164,10 @@ class ElggMemcache extends ElggSharedMemoryCache {
* @return mixed
*/
public function load($key, $offset = 0, $limit = null) {
- $key = $this->_makeMemcacheKey($key);
+ $key = $this->makeMemcacheKey($key);
$result = $this->memcache->get($key);
- if (!$result) {
+ if ($result === false) {
elgg_log("MEMCACHE: FAILED TO LOAD $key", 'ERROR');
}
@@ -191,7 +182,7 @@ class ElggMemcache extends ElggSharedMemoryCache {
* @return bool
*/
public function delete($key) {
- $key = $this->_makeMemcacheKey($key);
+ $key = $this->makeMemcacheKey($key);
return $this->memcache->delete($key, 0);
}
diff --git a/engine/classes/ElggMenuBuilder.php b/engine/classes/ElggMenuBuilder.php
new file mode 100644
index 000000000..b463143d8
--- /dev/null
+++ b/engine/classes/ElggMenuBuilder.php
@@ -0,0 +1,291 @@
+<?php
+/**
+ * Elgg Menu Builder
+ *
+ * @package Elgg.Core
+ * @subpackage Navigation
+ * @since 1.8.0
+ */
+class ElggMenuBuilder {
+
+ /**
+ * @var ElggMenuItem[]
+ */
+ protected $menu = array();
+
+ protected $selected = null;
+
+ /**
+ * ElggMenuBuilder constructor
+ *
+ * @param ElggMenuItem[] $menu Array of ElggMenuItem objects
+ */
+ public function __construct(array $menu) {
+ $this->menu = $menu;
+ }
+
+ /**
+ * Get a prepared menu array
+ *
+ * @param mixed $sort_by Method to sort the menu by. @see ElggMenuBuilder::sort()
+ * @return array
+ */
+ public function getMenu($sort_by = 'text') {
+
+ $this->selectFromContext();
+
+ $this->selected = $this->findSelected();
+
+ $this->setupSections();
+
+ $this->setupTrees();
+
+ $this->sort($sort_by);
+
+ return $this->menu;
+ }
+
+ /**
+ * Get the selected menu item
+ *
+ * @return ElggMenuItem
+ */
+ public function getSelected() {
+ return $this->selected;
+ }
+
+ /**
+ * Select menu items for the current context
+ *
+ * @return void
+ */
+ protected function selectFromContext() {
+ if (!isset($this->menu)) {
+ $this->menu = array();
+ return;
+ }
+
+ // get menu items for this context
+ $selected_menu = array();
+ foreach ($this->menu as $menu_item) {
+ if (!is_object($menu_item)) {
+ elgg_log("A non-object was passed to ElggMenuBuilder", "ERROR");
+ continue;
+ }
+ if ($menu_item->inContext()) {
+ $selected_menu[] = $menu_item;
+ }
+ }
+
+ $this->menu = $selected_menu;
+ }
+
+ /**
+ * Group the menu items into sections
+ *
+ * @return void
+ */
+ protected function setupSections() {
+ $sectioned_menu = array();
+ foreach ($this->menu as $menu_item) {
+ if (!isset($sectioned_menu[$menu_item->getSection()])) {
+ $sectioned_menu[$menu_item->getSection()] = array();
+ }
+ $sectioned_menu[$menu_item->getSection()][] = $menu_item;
+ }
+ $this->menu = $sectioned_menu;
+ }
+
+ /**
+ * Create trees for each menu section
+ *
+ * @internal The tree is doubly linked (parent and children links)
+ * @return void
+ */
+ protected function setupTrees() {
+ $menu_tree = array();
+
+ foreach ($this->menu as $key => $section) {
+ $parents = array();
+ $children = array();
+ // divide base nodes from children
+ foreach ($section as $menu_item) {
+ /* @var ElggMenuItem $menu_item */
+ $parent_name = $menu_item->getParentName();
+ if (!$parent_name) {
+ $parents[$menu_item->getName()] = $menu_item;
+ } else {
+ $children[] = $menu_item;
+ }
+ }
+
+ // attach children to parents
+ $iteration = 0;
+ $current_gen = $parents;
+ $next_gen = null;
+ while (count($children) && $iteration < 5) {
+ foreach ($children as $index => $menu_item) {
+ $parent_name = $menu_item->getParentName();
+ if (array_key_exists($parent_name, $current_gen)) {
+ $next_gen[$menu_item->getName()] = $menu_item;
+ if (!in_array($menu_item, $current_gen[$parent_name]->getData('children'))) {
+ $current_gen[$parent_name]->addChild($menu_item);
+ $menu_item->setParent($current_gen[$parent_name]);
+ }
+ unset($children[$index]);
+ }
+ }
+ $current_gen = $next_gen;
+ $iteration += 1;
+ }
+
+ // convert keys to indexes for first level of tree
+ $parents = array_values($parents);
+
+ $menu_tree[$key] = $parents;
+ }
+
+ $this->menu = $menu_tree;
+ }
+
+ /**
+ * Find the menu item that is currently selected
+ *
+ * @return ElggMenuItem
+ */
+ protected function findSelected() {
+
+ // do we have a selected menu item already
+ foreach ($this->menu as $menu_item) {
+ if ($menu_item->getSelected()) {
+ return $menu_item;
+ }
+ }
+
+ // scan looking for a selected item
+ foreach ($this->menu as $menu_item) {
+ if ($menu_item->getHref()) {
+ if (elgg_http_url_is_identical(current_page_url(), $menu_item->getHref())) {
+ $menu_item->setSelected(true);
+ return $menu_item;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sort the menu sections and trees
+ *
+ * @param mixed $sort_by Sort type as string or php callback
+ * @return void
+ */
+ protected function sort($sort_by) {
+
+ // sort sections
+ ksort($this->menu);
+
+ switch ($sort_by) {
+ case 'text':
+ $sort_callback = array('ElggMenuBuilder', 'compareByText');
+ break;
+ case 'name':
+ $sort_callback = array('ElggMenuBuilder', 'compareByName');
+ break;
+ case 'priority':
+ $sort_callback = array('ElggMenuBuilder', 'compareByWeight');
+ break;
+ case 'register':
+ // use registration order - usort breaks this
+ return;
+ break;
+ default:
+ if (is_callable($sort_by)) {
+ $sort_callback = $sort_by;
+ } else {
+ return;
+ }
+ break;
+ }
+
+ // sort each section
+ foreach ($this->menu as $index => $section) {
+ foreach ($section as $key => $node) {
+ $section[$key]->setData('original_order', $key);
+ }
+ usort($section, $sort_callback);
+ $this->menu[$index] = $section;
+
+ // depth first traversal of tree
+ foreach ($section as $root) {
+ $stack = array();
+ array_push($stack, $root);
+ while (!empty($stack)) {
+ $node = array_pop($stack);
+ /* @var ElggMenuItem $node */
+ $node->sortChildren($sort_callback);
+ $children = $node->getChildren();
+ if ($children) {
+ $stack = array_merge($stack, $children);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Compare two menu items by their display text
+ *
+ * @param ElggMenuItem $a Menu item
+ * @param ElggMenuItem $b Menu item
+ * @return bool
+ */
+ public static function compareByText($a, $b) {
+ $at = $a->getText();
+ $bt = $b->getText();
+
+ $result = strnatcmp($at, $bt);
+ if ($result === 0) {
+ return $a->getData('original_order') - $b->getData('original_order');
+ }
+ return $result;
+ }
+
+ /**
+ * Compare two menu items by their identifiers
+ *
+ * @param ElggMenuItem $a Menu item
+ * @param ElggMenuItem $b Menu item
+ * @return bool
+ */
+ public static function compareByName($a, $b) {
+ $an = $a->getName();
+ $bn = $b->getName();
+
+ $result = strcmp($an, $bn);
+ if ($result === 0) {
+ return $a->getData('original_order') - $b->getData('original_order');
+ }
+ return $result;
+ }
+
+ /**
+ * Compare two menu items by their priority
+ *
+ * @param ElggMenuItem $a Menu item
+ * @param ElggMenuItem $b Menu item
+ * @return bool
+ *
+ * @todo change name to compareByPriority
+ */
+ public static function compareByWeight($a, $b) {
+ $aw = $a->getWeight();
+ $bw = $b->getWeight();
+
+ if ($aw == $bw) {
+ return $a->getData('original_order') - $b->getData('original_order');
+ }
+ return $aw - $bw;
+ }
+}
diff --git a/engine/classes/ElggMenuItem.php b/engine/classes/ElggMenuItem.php
new file mode 100644
index 000000000..81ce6c099
--- /dev/null
+++ b/engine/classes/ElggMenuItem.php
@@ -0,0 +1,590 @@
+<?php
+/**
+ * Elgg Menu Item
+ *
+ * To create a menu item that is not a link, pass false for $href.
+ *
+ * @package Elgg.Core
+ * @subpackage Navigation
+ * @since 1.8.0
+ */
+class ElggMenuItem {
+
+ /**
+ * @var array Non-rendered data about the menu item
+ */
+ protected $data = array(
+ // string Identifier of the menu
+ 'name' => '',
+
+ // array Page contexts this menu item should appear on
+ 'contexts' => array('all'),
+
+ // string Menu section identifier
+ 'section' => 'default',
+
+ // int Smaller priorities float to the top
+ 'priority' => 100,
+
+ // bool Is this the currently selected menu item
+ 'selected' => false,
+
+ // string Identifier of this item's parent
+ 'parent_name' => '',
+
+ // ElggMenuItem The parent object or null
+ 'parent' => null,
+
+ // array Array of children objects or empty array
+ 'children' => array(),
+
+ // array Classes to apply to the li tag
+ 'itemClass' => array(),
+
+ // array Classes to apply to the anchor tag
+ 'linkClass' => array(),
+ );
+
+ /**
+ * @var string The menu display string
+ */
+ protected $text;
+
+ /**
+ * @var string The menu url
+ */
+ protected $href = null;
+
+ /**
+ * @var string Tooltip
+ */
+ protected $title = false;
+
+ /**
+ * @var string The string to display if link is clicked
+ */
+ protected $confirm = '';
+
+
+ /**
+ * ElggMenuItem constructor
+ *
+ * @param string $name Identifier of the menu item
+ * @param string $text Display text of the menu item
+ * @param string $href URL of the menu item (false if not a link)
+ */
+ public function __construct($name, $text, $href) {
+ //$this->name = $name;
+ $this->text = $text;
+ if ($href) {
+ $this->href = elgg_normalize_url($href);
+ } else {
+ $this->href = $href;
+ }
+
+ $this->data['name'] = $name;
+ }
+
+ /**
+ * ElggMenuItem factory method
+ *
+ * This static method creates an ElggMenuItem from an associative array.
+ * Required keys are name, text, and href.
+ *
+ * @param array $options Option array of key value pairs
+ *
+ * @return ElggMenuItem or NULL on error
+ */
+ public static function factory($options) {
+ if (!isset($options['name']) || !isset($options['text'])) {
+ return NULL;
+ }
+ if (!isset($options['href'])) {
+ $options['href'] = '';
+ }
+
+ $item = new ElggMenuItem($options['name'], $options['text'], $options['href']);
+ unset($options['name']);
+ unset($options['text']);
+ unset($options['href']);
+
+ // special catch in case someone uses context rather than contexts
+ if (isset($options['context'])) {
+ $options['contexts'] = $options['context'];
+ unset($options['context']);
+ }
+
+ // make sure contexts is set correctly
+ if (isset($options['contexts'])) {
+ $item->setContext($options['contexts']);
+ unset($options['contexts']);
+ }
+
+ if (isset($options['link_class'])) {
+ $item->setLinkClass($options['link_class']);
+ unset($options['link_class']);
+ }
+
+ if (isset($options['item_class'])) {
+ $item->setItemClass($options['item_class']);
+ unset($options['item_class']);
+ }
+
+ if (isset($options['data']) && is_array($options['data'])) {
+ $item->setData($options['data']);
+ unset($options['data']);
+ }
+
+ foreach ($options as $key => $value) {
+ if (isset($item->data[$key])) {
+ $item->data[$key] = $value;
+ } else {
+ $item->$key = $value;
+ }
+ }
+
+ return $item;
+ }
+
+ /**
+ * Set a data key/value pair or a set of key/value pairs
+ *
+ * This method allows storage of arbitrary data with this menu item. The
+ * data can be used for sorting, custom rendering, or any other use.
+ *
+ * @param mixed $key String key or an associative array of key/value pairs
+ * @param mixed $value The value if $key is a string
+ * @return void
+ */
+ public function setData($key, $value = null) {
+ if (is_array($key)) {
+ $this->data += $key;
+ } else {
+ $this->data[$key] = $value;
+ }
+ }
+
+ /**
+ * Get stored data
+ *
+ * @param string $key The key for the requested key/value pair
+ * @return mixed
+ */
+ public function getData($key) {
+ if (isset($this->data[$key])) {
+ return $this->data[$key];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Set the identifier of the menu item
+ *
+ * @param string $name Unique identifier
+ * @return void
+ */
+ public function setName($name) {
+ $this->data['name'] = $name;
+ }
+
+ /**
+ * Get the identifier of the menu item
+ *
+ * @return string
+ */
+ public function getName() {
+ return $this->data['name'];
+ }
+
+ /**
+ * Set the display text of the menu item
+ *
+ * @param string $text The display text
+ * @return void
+ */
+ public function setText($text) {
+ $this->text = $text;
+ }
+
+ /**
+ * Get the display text of the menu item
+ *
+ * @return string
+ */
+ public function getText() {
+ return $this->text;
+ }
+
+ /**
+ * Set the URL of the menu item
+ *
+ * @param string $href URL or false if not a link
+ * @return void
+ */
+ public function setHref($href) {
+ $this->href = $href;
+ }
+
+ /**
+ * Get the URL of the menu item
+ *
+ * @return string
+ */
+ public function getHref() {
+ return $this->href;
+ }
+
+ /**
+ * Set the contexts that this menu item is available for
+ *
+ * @param array $contexts An array of context strings
+ * @return void
+ */
+ public function setContext($contexts) {
+ if (is_string($contexts)) {
+ $contexts = array($contexts);
+ }
+ $this->data['contexts'] = $contexts;
+ }
+
+ /**
+ * Get an array of context strings
+ *
+ * @return array
+ */
+ public function getContext() {
+ return $this->data['contexts'];
+ }
+
+ /**
+ * Should this menu item be used given the current context
+ *
+ * @param string $context A context string (default is empty string for
+ * current context stack).
+ * @return bool
+ */
+ public function inContext($context = '') {
+ if ($context) {
+ return in_array($context, $this->data['contexts']);
+ }
+
+ if (in_array('all', $this->data['contexts'])) {
+ return true;
+ }
+
+ foreach ($this->data['contexts'] as $context) {
+ if (elgg_in_context($context)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Set the selected flag
+ *
+ * @param bool $state Selected state (default is true)
+ * @return void
+ */
+ public function setSelected($state = true) {
+ $this->data['selected'] = $state;
+ }
+
+ /**
+ * Get selected state
+ *
+ * @return bool
+ */
+ public function getSelected() {
+ return $this->data['selected'];
+ }
+
+ /**
+ * Set the tool tip text
+ *
+ * @param string $text The text of the tool tip
+ * @return void
+ */
+ public function setTooltip($text) {
+ $this->title = $text;
+ }
+
+ /**
+ * Get the tool tip text
+ *
+ * @return string
+ */
+ public function getTooltip() {
+ return $this->title;
+ }
+
+ /**
+ * Set the confirm text shown when link is clicked
+ *
+ * @param string $text The text to show
+ * @return void
+ */
+ public function setConfirmText($text) {
+ $this->confirm = $text;
+ }
+
+ /**
+ * Get the confirm text
+ *
+ * @return string
+ */
+ public function getConfirmText() {
+ return $this->confirm;
+ }
+
+ /**
+ * Set the anchor class
+ *
+ * @param mixed $class An array of class names, or a single string class name.
+ * @return void
+ */
+ public function setLinkClass($class) {
+ if (!is_array($class)) {
+ $this->data['linkClass'] = array($class);
+ } else {
+ $this->data['linkClass'] = $class;
+ }
+ }
+
+ /**
+ * Get the anchor classes as text
+ *
+ * @return string
+ */
+ public function getLinkClass() {
+ return implode(' ', $this->data['linkClass']);
+ }
+
+ /**
+ * Add a link class
+ *
+ * @param mixed $class An array of class names, or a single string class name.
+ * @return void
+ */
+ public function addLinkClass($class) {
+ if (!is_array($class)) {
+ $this->data['linkClass'][] = $class;
+ } else {
+ $this->data['linkClass'] += $class;
+ }
+ }
+
+ /**
+ * Set the li classes
+ *
+ * @param mixed $class An array of class names, or a single string class name.
+ * @return void
+ */
+ public function setItemClass($class) {
+ if (!is_array($class)) {
+ $this->data['itemClass'] = array($class);
+ } else {
+ $this->data['itemClass'] = $class;
+ }
+ }
+
+ /**
+ * Get the li classes as text
+ *
+ * @return string
+ */
+ public function getItemClass() {
+ // allow people to specify name with underscores and colons
+ $name = strtolower($this->getName());
+ $name = str_replace('_', '-', $name);
+ $name = str_replace(':', '-', $name);
+ $name = str_replace(' ', '-', $name);
+
+ $class = implode(' ', $this->data['itemClass']);
+ if ($class) {
+ return "elgg-menu-item-$name $class";
+ } else {
+ return "elgg-menu-item-$name";
+ }
+ }
+
+ /**
+ * Set the priority of the menu item
+ *
+ * @param int $priority The smaller numbers mean higher priority (1 before 100)
+ * @return void
+ * @deprecated
+ */
+ public function setWeight($priority) {
+ $this->data['priority'] = $priority;
+ }
+
+ /**
+ * Get the priority of the menu item
+ *
+ * @return int
+ * @deprecated
+ */
+ public function getWeight() {
+ return $this->data['priority'];
+ }
+
+ /**
+ * Set the priority of the menu item
+ *
+ * @param int $priority The smaller numbers mean higher priority (1 before 100)
+ * @return void
+ */
+ public function setPriority($priority) {
+ $this->data['priority'] = $priority;
+ }
+
+ /**
+ * Get the priority of the menu item
+ *
+ * @return int
+ */
+ public function getPriority() {
+ return $this->data['priority'];
+ }
+
+ /**
+ * Set the section identifier
+ *
+ * @param string $section The identifier of the section
+ * @return void
+ */
+ public function setSection($section) {
+ $this->data['section'] = $section;
+ }
+
+ /**
+ * Get the section identifier
+ *
+ * @return string
+ */
+ public function getSection() {
+ return $this->data['section'];
+ }
+
+ /**
+ * Set the parent identifier
+ *
+ * @param string $name The identifier of the parent ElggMenuItem
+ * @return void
+ */
+ public function setParentName($name) {
+ $this->data['parent_name'] = $name;
+ }
+
+ /**
+ * Get the parent identifier
+ *
+ * @return string
+ */
+ public function getParentName() {
+ return $this->data['parent_name'];
+ }
+
+ /**
+ * Set the parent menu item
+ *
+ * @param ElggMenuItem $parent The parent of this menu item
+ * @return void
+ */
+ public function setParent($parent) {
+ $this->data['parent'] = $parent;
+ }
+
+ /**
+ * Get the parent menu item
+ *
+ * @return ElggMenuItem or null
+ */
+ public function getParent() {
+ return $this->data['parent'];
+ }
+
+ /**
+ * Add a child menu item
+ *
+ * @param ElggMenuItem $item A child menu item
+ * @return void
+ */
+ public function addChild($item) {
+ $this->data['children'][] = $item;
+ }
+
+ /**
+ * Set the menu item's children
+ *
+ * @param array $children Array of ElggMenuItems
+ * @return void
+ */
+ public function setChildren($children) {
+ $this->data['children'] = $children;
+ }
+
+ /**
+ * Get the children menu items
+ *
+ * @return array
+ */
+ public function getChildren() {
+ return $this->data['children'];
+ }
+
+ /**
+ * Sort the children
+ *
+ * @param string $sortFunction A function that is passed to usort()
+ * @return void
+ */
+ public function sortChildren($sortFunction) {
+ foreach ($this->data['children'] as $key => $node) {
+ $this->data['children'][$key]->data['original_order'] = $key;
+ }
+ usort($this->data['children'], $sortFunction);
+ }
+
+ /**
+ * Get the menu item content (usually a link)
+ *
+ * @param array $vars Options to pass to output/url if a link
+ * @return string
+ * @todo View code in a model. How do we feel about that?
+ */
+ public function getContent(array $vars = array()) {
+
+ if ($this->href === false) {
+ return $this->text;
+ }
+
+ $defaults = get_object_vars($this);
+ unset($defaults['data']);
+
+ $vars += $defaults;
+
+ if ($this->data['linkClass']) {
+ if (isset($vars['class'])) {
+ $vars['class'] = $vars['class'] . ' ' . $this->getLinkClass();
+ } else {
+ $vars['class'] = $this->getLinkClass();
+ }
+ }
+
+ if (!isset($vars['rel']) && !isset($vars['is_trusted'])) {
+ $vars['is_trusted'] = true;
+ }
+
+ if ($this->confirm) {
+ $vars['confirm'] = $this->confirm;
+ return elgg_view('output/confirmlink', $vars);
+ } else {
+ unset($vars['confirm']);
+ }
+
+ return elgg_view('output/url', $vars);
+ }
+}
diff --git a/engine/classes/ElggMetadata.php b/engine/classes/ElggMetadata.php
index 12e458629..3a8e2d817 100644
--- a/engine/classes/ElggMetadata.php
+++ b/engine/classes/ElggMetadata.php
@@ -6,9 +6,20 @@
*
* @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();
@@ -16,11 +27,9 @@ class ElggMetadata extends ElggExtender {
}
/**
- * Construct a new site object, optionally from a given id value or row.
+ * Construct a metadata object
*
- * @param mixed $id ID of metadata from DB
- *
- * @return void
+ * @param mixed $id ID of metadata or a database row as stdClass object
*/
function __construct($id = null) {
$this->initializeAttributes();
@@ -29,15 +38,15 @@ class ElggMetadata extends ElggExtender {
// Create from db row
if ($id instanceof stdClass) {
$metadata = $id;
- } else {
- $metadata = get_metadata($id);
- }
-
- if ($metadata) {
+
$objarray = (array) $metadata;
foreach ($objarray as $key => $value) {
$this->attributes[$key] = $value;
}
+ } else {
+ // get an ElggMetadata object and copy its attributes
+ $metadata = elgg_get_metadata_from_id($id);
+ $this->attributes = $metadata->attributes;
}
}
}
@@ -45,19 +54,23 @@ class ElggMetadata extends ElggExtender {
/**
* Determines whether or not the user can edit this piece of metadata
*
- * @return true|false Depending on permissions
+ * @param int $user_guid The GUID of the user (defaults to currently logged in user)
+ *
+ * @return bool Depending on permissions
*/
- function canEdit() {
+ function canEdit($user_guid = 0) {
if ($entity = get_entity($this->get('entity_guid'))) {
- return $entity->canEditMetadata($this);
+ return $entity->canEditMetadata($this, $user_guid);
}
return false;
}
/**
- * Save matadata object
+ * Save metadata object
+ *
+ * @return int|bool the metadata object id or true if updated
*
- * @return int the metadata object id
+ * @throws IOException
*/
function save() {
if ($this->id > 0) {
@@ -75,12 +88,48 @@ class ElggMetadata extends ElggExtender {
}
/**
- * Delete a given metadata.
+ * Delete the metadata
*
* @return bool
*/
function delete() {
- return delete_metadata($this->id);
+ $success = elgg_delete_metastring_based_object_by_id($this->id, 'metadata');
+ if ($success) {
+ // we mark unknown here because this deletes only one value
+ // under this name, and there may be others remaining.
+ elgg_get_metadata_cache()->markUnknown($this->entity_guid, $this->name);
+ }
+ return $success;
+ }
+
+ /**
+ * Disable the metadata
+ *
+ * @return bool
+ * @since 1.8
+ */
+ function disable() {
+ $success = elgg_set_metastring_based_object_enabled_by_id($this->id, 'no', 'metadata');
+ if ($success) {
+ // we mark unknown here because this disables only one value
+ // under this name, and there may be others remaining.
+ elgg_get_metadata_cache()->markUnknown($this->entity_guid, $this->name);
+ }
+ return $success;
+ }
+
+ /**
+ * Enable the metadata
+ *
+ * @return bool
+ * @since 1.8
+ */
+ function enable() {
+ $success = elgg_set_metastring_based_object_enabled_by_id($this->id, 'yes', 'metadata');
+ if ($success) {
+ elgg_get_metadata_cache()->markUnknown($this->entity_guid, $this->name);
+ }
+ return $success;
}
/**
@@ -104,6 +153,6 @@ class ElggMetadata extends ElggExtender {
* @return ElggMetadata
*/
public function getObjectFromID($id) {
- return get_metadata($id);
+ return elgg_get_metadata_from_id($id);
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggObject.php b/engine/classes/ElggObject.php
index 1d15bb305..aeaa3ba5c 100644
--- a/engine/classes/ElggObject.php
+++ b/engine/classes/ElggObject.php
@@ -14,6 +14,10 @@
*
* @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 {
@@ -37,12 +41,12 @@ class ElggObject extends ElggEntity {
*
* If no arguments are passed, create a new entity.
*
- * If an argument is passed attempt to load a full Object entity. Arguments
- * can be:
+ * If an argument is passed, attempt to load a full ElggObject entity.
+ * Arguments can be:
* - The GUID of an object entity.
- * - A DB result object with a guid property
+ * - A DB result object from the entities table with a guid property
*
- * @param mixed $guid If an int, load that GUID. If a db row then will attempt to
+ * @param mixed $guid If an int, load that GUID. If a db row, then will attempt to
* load the rest of the data.
*
* @throws IOException If passed an incorrect guid
@@ -55,28 +59,25 @@ class ElggObject extends ElggEntity {
$this->initialise_attributes(false);
if (!empty($guid)) {
- // Is $guid is a DB row - either a entity row, or a object table row.
+ // Is $guid is a DB row from the entity table
if ($guid instanceof stdClass) {
// Load the rest
- if (!$this->load($guid->guid)) {
+ if (!$this->load($guid)) {
$msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid));
throw new IOException($msg);
}
-
- // Is $guid is an ElggObject? Use a copy constructor
} else if ($guid instanceof ElggObject) {
+ // $guid is an ElggObject so this is a copy constructor
elgg_deprecated_notice('This type of usage of the ElggObject constructor was deprecated. Please use the clone method.', 1.7);
foreach ($guid->attributes as $key => $value) {
$this->attributes[$key] = $value;
}
-
- // Is this is an ElggEntity but not an ElggObject = ERROR!
} else if ($guid instanceof ElggEntity) {
+ // @todo remove - do not need separate exception
throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggObject'));
-
- // We assume if we have got this far, $guid is an int
} else if (is_numeric($guid)) {
+ // $guid is a GUID so load
if (!$this->load($guid)) {
throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid)));
}
@@ -89,35 +90,24 @@ class ElggObject extends ElggEntity {
/**
* Loads the full ElggObject when given a guid.
*
- * @param int $guid Guid of an ElggObject
+ * @param mixed $guid GUID of an ElggObject or the stdClass object from entities table
*
* @return bool
* @throws InvalidClassException
*/
protected function load($guid) {
- // Test to see if we have the generic stuff
- if (!parent::load($guid)) {
- return false;
- }
-
- // Check the type
- if ($this->attributes['type'] != 'object') {
- $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class()));
- throw new InvalidClassException($msg);
- }
+ $attr_loader = new ElggAttributeLoader(get_class(), 'object', $this->attributes);
+ $attr_loader->requires_access_control = !($this instanceof ElggPlugin);
+ $attr_loader->secondary_loader = 'get_object_entity_as_row';
- // Load missing data
- $row = get_object_entity_as_row($guid);
- if (($row) && (!$this->isFullyLoaded())) {
- // If $row isn't a cached copy then increment the counter
- $this->attributes['tables_loaded'] ++;
+ $attrs = $attr_loader->getRequiredAttributes($guid);
+ if (!$attrs) {
+ return false;
}
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach ($objarray as $key => $value) {
- $this->attributes[$key] = $value;
- }
+ $this->attributes = $attrs;
+ $this->attributes['tables_loaded'] = 2;
+ _elgg_cache_entity($this);
return true;
}
@@ -136,8 +126,12 @@ class ElggObject extends ElggEntity {
}
// Save ElggObject-specific attributes
- return create_object_entity($this->get('guid'), $this->get('title'),
- $this->get('description'), $this->get('container_guid'));
+
+ _elgg_disable_caching_for_entity($this->guid);
+ $ret = create_object_entity($this->get('guid'), $this->get('title'), $this->get('description'));
+ _elgg_enable_caching_for_entity($this->guid);
+
+ return $ret;
}
/**
@@ -184,4 +178,39 @@ class ElggObject extends ElggEntity {
'description',
));
}
-} \ No newline at end of file
+
+ /**
+ * Can a user comment on this object?
+ *
+ * @see ElggEntity::canComment()
+ *
+ * @param int $user_guid User guid (default is logged in user)
+ * @return bool
+ * @since 1.8.0
+ */
+ public function canComment($user_guid = 0) {
+ $result = parent::canComment($user_guid);
+ if ($result !== null) {
+ return $result;
+ }
+
+ if ($user_guid == 0) {
+ $user_guid = elgg_get_logged_in_user_guid();
+ }
+
+ // must be logged in to comment
+ if (!$user_guid) {
+ return false;
+ }
+
+ // must be member of group
+ if (elgg_instanceof($this->getContainerEntity(), 'group')) {
+ if (!$this->getContainerEntity()->canWriteToContainer($user_guid)) {
+ return false;
+ }
+ }
+
+ // no checks on read access since a user cannot see entities outside his access
+ return true;
+ }
+}
diff --git a/engine/classes/ElggPAM.php b/engine/classes/ElggPAM.php
index 37436fba3..f07095fc1 100644
--- a/engine/classes/ElggPAM.php
+++ b/engine/classes/ElggPAM.php
@@ -41,18 +41,29 @@ class ElggPAM {
* @param array $credentials Credentials array dependant on policy type
* @return bool
*/
- public function authenticate($credentials) {
+ 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
- $result = $handler($credentials);
+ // @todo don't assume $handler is a global function
+ $result = call_user_func($handler, $credentials);
if ($result) {
$authenticated = true;
} elseif ($result === false) {
diff --git a/engine/classes/ElggPlugin.php b/engine/classes/ElggPlugin.php
index 887eb667f..545b9a53c 100644
--- a/engine/classes/ElggPlugin.php
+++ b/engine/classes/ElggPlugin.php
@@ -9,6 +9,12 @@
* @subpackage Plugins.Settings
*/
class ElggPlugin extends ElggObject {
+ private $package;
+ private $manifest;
+
+ private $path;
+ private $pluginID;
+ private $errorMsg = '';
/**
* Set subtype to 'plugin'
@@ -19,8 +25,839 @@ class ElggPlugin extends ElggObject {
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.
@@ -30,14 +867,22 @@ class ElggPlugin extends ElggObject {
* @return mixed
*/
public function get($name) {
+ // rewrite for old and inaccurate plugin:setting
+ if (strstr($name, 'plugin:setting:')) {
+ $msg = 'Direct access of user settings is deprecated. Use ElggPlugin->getUserSetting()';
+ elgg_deprecated_notice($msg, 1.8);
+ $name = str_replace('plugin:setting:', '', $name);
+ $name = elgg_namespace_plugin_private_setting('user_setting', $name);
+ }
+
// See if its in our base attribute
- if (isset($this->attributes[$name])) {
+ if (array_key_exists($name, $this->attributes)) {
return $this->attributes[$name];
}
// No, so see if its in the private data store.
// get_private_setting() returns false if it doesn't exist
- $meta = get_private_setting($this->guid, $name);
+ $meta = $this->getPrivateSetting($name);
if ($meta === false) {
// Can't find it, so return null
@@ -48,7 +893,9 @@ class ElggPlugin extends ElggObject {
}
/**
- * Save a value to private settings.
+ * Save a value as private setting or attribute.
+ *
+ * Attributes include title and description.
*
* @param string $name Name
* @param mixed $value Value
@@ -63,10 +910,97 @@ class ElggPlugin extends ElggObject {
}
$this->attributes[$name] = $value;
+
+ return true;
} else {
- return set_private_setting($this->guid, $name, $value);
+ // Hook to validate setting
+ $value = elgg_trigger_plugin_hook('setting', 'plugin', array(
+ 'plugin_id' => $this->pluginID,
+ 'plugin' => $this,
+ 'name' => $name,
+ 'value' => $value
+ ), $value);
+
+ return $this->setPrivateSetting($name, $value);
+ }
+ }
+
+ /**
+ * Sets the plugin to active or inactive for $site_guid.
+ *
+ * @param bool $active Set to active or inactive
+ * @param mixed $site_guid Int for specific site, null for current site.
+ *
+ * @return bool
+ */
+ private function setStatus($active, $site_guid = null) {
+ if (!$this->guid) {
+ return false;
}
- return true;
+ if ($site_guid) {
+ $site = get_entity($site_guid);
+
+ if (!($site instanceof ElggSite)) {
+ return false;
+ }
+ } else {
+ $site = get_config('site');
+ }
+
+ if ($active) {
+ return add_entity_relationship($this->guid, 'active_plugin', $site->guid);
+ } else {
+ return remove_entity_relationship($this->guid, 'active_plugin', $site->guid);
+ }
+ }
+
+ /**
+ * Returns the last error message registered.
+ *
+ * @return string|null
+ */
+ public function getError() {
+ return $this->errorMsg;
+ }
+
+ /**
+ * Returns this plugin's ElggPluginManifest object
+ *
+ * @return ElggPluginManifest
+ */
+ public function getManifest() {
+ if ($this->manifest instanceof ElggPluginManifest) {
+ return $this->manifest;
+ }
+
+ try {
+ $this->manifest = $this->getPackage()->getManifest();
+ } catch (Exception $e) {
+ elgg_log("Failed to load manifest for plugin $this->guid. " . $e->getMessage(), 'WARNING');
+ $this->errorMsg = $e->getmessage();
+ }
+
+ return $this->manifest;
+ }
+
+ /**
+ * Returns this plugin's ElggPluginPackage object
+ *
+ * @return ElggPluginPackage
+ */
+ public function getPackage() {
+ if ($this->package instanceof ElggPluginPackage) {
+ return $this->package;
+ }
+
+ try {
+ $this->package = new ElggPluginPackage($this->path, false);
+ } catch (Exception $e) {
+ elgg_log("Failed to load package for $this->guid. " . $e->getMessage(), 'WARNING');
+ $this->errorMsg = $e->getmessage();
+ }
+
+ return $this->package;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggPluginManifest.php b/engine/classes/ElggPluginManifest.php
index 801769eb9..6912c2b08 100644
--- a/engine/classes/ElggPluginManifest.php
+++ b/engine/classes/ElggPluginManifest.php
@@ -8,8 +8,8 @@
* as $this->parser.
*
* To add new parser versions, name them ElggPluginManifestParserXX
- * where XX is the version specified in the top-level <plugin-manifest>
- * tag.
+ * where XX is the version specified in the top-level <plugin_manifest>
+ * tag's XML namespace.
*
* @package Elgg.Core
* @subpackage Plugins
@@ -23,19 +23,34 @@ class ElggPluginManifest {
protected $parser;
/**
- * The expected structure of a requires element
+ * The root for plugin manifest namespaces.
+ * This is in the format http://www.elgg.org/plugin_manifest/<version>
*/
- private $depsRequiresStructPlugin = array(
+ 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 and elgg_release requires element
+ * The expected structure of elgg_version and elgg_release requires element
*/
- private $depsRequiresStructElgg = array(
+ private $depsStructElgg = array(
'type' => '',
'version' => '',
'comparison' => 'ge'
@@ -44,7 +59,7 @@ class ElggPluginManifest {
/**
* The expected structure of a requires php_ini dependency element
*/
- private $depsRequiresStructPhpIni = array(
+ private $depsStructPhpIni = array(
'type' => '',
'name' => '',
'value' => '',
@@ -54,7 +69,7 @@ class ElggPluginManifest {
/**
* The expected structure of a requires php_extension dependency element
*/
- private $depsRequiresStructPhpExtension = array(
+ private $depsStructPhpExtension = array(
'type' => '',
'name' => '',
'version' => '',
@@ -115,7 +130,7 @@ class ElggPluginManifest {
}
// see if we need to construct the xml object.
- if ($manifest instanceof XmlElement) {
+ if ($manifest instanceof ElggXMLElement) {
$manifest_obj = $manifest;
} else {
if (substr(trim($manifest), 0, 1) == '<') {
@@ -135,12 +150,15 @@ class ElggPluginManifest {
}
// set manifest api version
- if (isset($manifest_obj->attributes['version'])) {
- $this->apiVersion = (float)$manifest_obj->attributes['version'];
+ if (isset($manifest_obj->attributes['xmlns'])) {
+ $namespace = $manifest_obj->attributes['xmlns'];
+ $version = str_replace($this->namespace_root, '', $namespace);
} else {
- $this->apiVersion = 1.7;
+ $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.
@@ -225,7 +243,7 @@ class ElggPluginManifest {
* @return string
*/
public function getDescription() {
- return elgg_echo($this->parser->getAttribute('description'));
+ return $this->parser->getAttribute('description');
}
/**
@@ -234,7 +252,7 @@ class ElggPluginManifest {
* @return string
*/
public function getBlurb() {
- $blurb = elgg_echo($this->parser->getAttribute('blurb'));
+ $blurb = $this->parser->getAttribute('blurb');
if (!$blurb) {
$blurb = elgg_get_excerpt($this->getDescription());
@@ -246,12 +264,44 @@ class ElggPluginManifest {
/**
* Returns the license
*
- * @return sting
+ * @return string
*/
public function getLicense() {
- return $this->parser->getAttribute('license');
+ // 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.
@@ -295,12 +345,26 @@ class ElggPluginManifest {
* @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;
}
@@ -330,7 +394,12 @@ class ElggPluginManifest {
* @return array
*/
public function getProvides() {
- $provides = $this->parser->getAttribute('provides');
+ // normalize for 1.7
+ if ($this->getApiVersion() < 1.8) {
+ $provides = array();
+ } else {
+ $provides = $this->parser->getAttribute('provides');
+ }
if (!$provides) {
$provides = array();
@@ -370,6 +439,8 @@ class ElggPluginManifest {
'comparison' => 'ge'
)
);
+ } else {
+ $reqs = array();
}
} else {
$reqs = $this->parser->getAttribute('requires');
@@ -381,82 +452,120 @@ class ElggPluginManifest {
$normalized = array();
foreach ($reqs as $req) {
+ $normalized[] = $this->normalizeDep($req);
+ }
- switch ($req['type']) {
- case 'elgg_version':
- case 'elgg_release':
- $struct = $this->depsRequiresStructElgg;
- break;
+ return $normalized;
+ }
- case 'plugin':
- $struct = $this->depsRequiresStructPlugin;
- break;
+ /**
+ * Returns the suggests elements.
+ *
+ * @return array
+ */
+ public function getSuggests() {
+ $suggests = $this->parser->getAttribute('suggests');
- case 'php_extension':
- $struct = $this->depsRequiresStructPhpExtension;
- break;
+ if (!$suggests) {
+ $suggests = array();
+ }
- case 'php_ini':
- $struct = $this->depsRequiresStructPhpIni;
-
- // also normalize boolean values
- if (isset($req['value'])) {
- switch (strtolower($normalized_req['value'])) {
- case 'yes':
- case 'true':
- case 'on':
- case 1:
- $normalized_req['value'] = 1;
- break;
-
- case 'no':
- case 'false':
- case 'off':
- case 0:
- case '':
- $normalized_req['value'] = 0;
- break;
- }
- }
+ $normalized = array();
+ foreach ($suggests as $suggest) {
+ $normalized[] = $this->normalizeDep($suggest);
+ }
- break;
- }
+ 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_req = $this->buildStruct($struct, $req);
+ $normalized_dep = $this->buildStruct($struct, $dep);
- // normalize comparison operators
- switch ($normalized_req['comparison']) {
+ // normalize comparison operators
+ if (isset($normalized_dep['comparison'])) {
+ switch ($normalized_dep['comparison']) {
case '<':
- $normalized_req['comparison'] = 'lt';
+ $normalized_dep['comparison'] = 'lt';
break;
case '<=':
- $normalized_req['comparison'] = 'le';
+ $normalized_dep['comparison'] = 'le';
break;
case '>':
- $normalized_req['comparison'] = 'gt';
+ $normalized_dep['comparison'] = 'gt';
break;
case '>=':
- $normalized_req['comparison'] = 'ge';
+ $normalized_dep['comparison'] = 'ge';
break;
case '==':
case 'eq':
- $normalized_req['comparison'] = '=';
+ $normalized_dep['comparison'] = '=';
break;
case '<>':
case 'ne':
- $normalized_req['comparison'] = '!=';
+ $normalized_dep['comparison'] = '!=';
break;
}
-
- $normalized[] = $normalized_req;
}
- return $normalized;
+ return $normalized_dep;
}
/**
@@ -465,7 +574,12 @@ class ElggPluginManifest {
* @return array
*/
public function getConflicts() {
- $conflicts = $this->parser->getAttribute('conflicts');
+ // normalize for 1.7
+ if ($this->getApiVersion() < 1.8) {
+ $conflicts = array();
+ } else {
+ $conflicts = $this->parser->getAttribute('conflicts');
+ }
if (!$conflicts) {
$conflicts = array();
@@ -481,6 +595,29 @@ class ElggPluginManifest {
}
/**
+ * 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.
@@ -492,9 +629,28 @@ class ElggPluginManifest {
$return = array();
foreach ($struct as $index => $default) {
- $return[$index] = elgg_get_array_value($index, $array, $default);
+ $return[$index] = elgg_extract($index, $array, $default);
}
return $return;
}
-} \ No newline at end of file
+
+ /**
+ * 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
index dce46cbb4..af152b561 100644
--- a/engine/classes/ElggPluginManifestParser.php
+++ b/engine/classes/ElggPluginManifestParser.php
@@ -53,10 +53,10 @@ abstract class ElggPluginManifestParser {
/**
* Loads the manifest XML to be parsed.
*
- * @param XmlElement $xml The Manifest XML object to be parsed
- * @param object $caller The object calling this parser.
+ * @param ElggXmlElement $xml The Manifest XML object to be parsed
+ * @param object $caller The object calling this parser.
*/
- public function __construct(XmlElement $xml, $caller) {
+ public function __construct(ElggXMLElement $xml, $caller) {
$this->manifestObject = $xml;
$this->caller = $caller;
}
@@ -99,4 +99,4 @@ abstract class ElggPluginManifestParser {
* @return bool
*/
abstract public function parse();
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggPluginManifestParser17.php b/engine/classes/ElggPluginManifestParser17.php
index 8e4a79e4f..5658ee804 100644
--- a/engine/classes/ElggPluginManifestParser17.php
+++ b/engine/classes/ElggPluginManifestParser17.php
@@ -12,10 +12,13 @@ class ElggPluginManifestParser17 extends ElggPluginManifestParser {
*/
protected $validAttributes = array(
'author', 'version', 'description', 'website',
- 'copyright', 'license', 'elgg_version',
+ 'copyright', 'license', 'licence', 'elgg_version',
// were never really used and not enforced in code.
- 'requires', 'recommends', 'conflicts'
+ 'requires', 'recommends', 'conflicts',
+
+ // not a 1.7 field, but we need it
+ 'name',
);
/**
@@ -28,6 +31,8 @@ class ElggPluginManifestParser17 extends ElggPluginManifestParser {
return false;
}
+ $elements = array();
+
foreach ($this->manifestObject->children as $element) {
$key = $element->attributes['key'];
$value = $element->attributes['value'];
@@ -45,6 +50,10 @@ class ElggPluginManifestParser17 extends ElggPluginManifestParser {
}
}
+ if ($elements && !array_key_exists('name', $elements)) {
+ $elements['name'] = $this->caller->getName();
+ }
+
$this->manifest = $elements;
if (!$this->manifest) {
@@ -70,4 +79,4 @@ class ElggPluginManifestParser17 extends ElggPluginManifestParser {
return false;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggPluginManifestParser18.php b/engine/classes/ElggPluginManifestParser18.php
index 9a4cfb2b7..3b753f17b 100644
--- a/engine/classes/ElggPluginManifestParser18.php
+++ b/engine/classes/ElggPluginManifestParser18.php
@@ -13,9 +13,10 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser {
* @var array
*/
protected $validAttributes = array(
- 'name', 'author', 'version', 'blurb', 'description',
- 'website', 'copyright', 'license', 'requires', 'screenshot',
- 'category', 'conflicts', 'provides', 'admin'
+ 'name', 'author', 'version', 'blurb', 'description','website',
+ 'repository', 'bugtracker', 'donations', 'copyright', 'license',
+ 'requires', 'suggests', 'conflicts', 'provides',
+ 'screenshot', 'category', 'activate_on_install'
);
/**
@@ -45,30 +46,24 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser {
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['category'][] = $element->content;
- break;
-
- case 'admin':
- $parsed['admin'] = array();
- if (!isset($element->children)) {
- return false;
- }
-
- foreach ($element->children as $child_element) {
- $parsed['admin'][$child_element->name] = $child_element->content;
- }
-
+ $parsed[$element->name][] = $element->content;
break;
+ // 3d arrays
case 'screenshot':
case 'provides':
case 'conflicts':
case 'requires':
+ case 'suggests':
if (!isset($element->children)) {
return false;
}
@@ -99,4 +94,4 @@ class ElggPluginManifestParser18 extends ElggPluginManifestParser {
return true;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggPluginPackage.php b/engine/classes/ElggPluginPackage.php
index 4a10da43d..37eb4bf4d 100644
--- a/engine/classes/ElggPluginPackage.php
+++ b/engine/classes/ElggPluginPackage.php
@@ -28,6 +28,17 @@ class ElggPluginPackage {
);
/**
+ * 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
@@ -42,13 +53,13 @@ class ElggPluginPackage {
* @var array
*/
private $depsSupportedTypes = array(
- 'elgg_version', 'elgg_release', 'php_extension', 'php_ini', 'plugin'
+ 'elgg_version', 'elgg_release', 'php_extension', 'php_ini', 'plugin', 'priority',
);
/**
* An invalid plugin error.
*/
- private $invalidPluginError = '';
+ private $errorMsg = '';
/**
* Any dependencies messages
@@ -89,38 +100,41 @@ class ElggPluginPackage {
* @param string $plugin The ID (directory name) or full path of the plugin.
* @param bool $validate Automatically run isValid()?
*
- * @return true
* @throws PluginException
*/
public function __construct($plugin, $validate = true) {
- if (substr($plugin, 0, 1) == '/') {
+ $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
- $plugin = sanitise_filepath($plugin);
-
- if (!is_dir($plugin)) {
- throw new PluginException(elgg_echo('PluginException:InvalidPath', array($plugin)));
- }
+ $path = sanitise_filepath($plugin);
// the id is the last element of the array
- $path_array = explode('/', trim($plugin, '/'));
- $this->id = array_pop($path_array);
- $this->path = $plugin;
+ $path_array = explode('/', trim($path, '/'));
+ $id = array_pop($path_array);
} else {
- // this is a plugin name
-
+ // this is a plugin id
// strict plugin names
- if (preg_match('/[^a-z0-9\.\-_]/i', $id)) {
+ if (preg_match('/[^a-z0-9\.\-_]/i', $plugin)) {
throw new PluginException(elgg_echo('PluginException:InvalidID', array($plugin)));
}
- $this->id = $plugin;
- $this->path = get_config('pluginspath') . "$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->invalidPluginError) {
+ if ($this->errorMsg) {
throw new PluginException(elgg_echo('PluginException:InvalidPlugin:Details',
- array($plugin, $this->invalidPluginError)));
+ array($plugin, $this->errorMsg)));
} else {
throw new PluginException(elgg_echo('PluginException:InvalidPlugin', array($plugin)));
}
@@ -151,14 +165,12 @@ class ElggPluginPackage {
return $this->valid;
}
- $valid = true;
-
// check required files.
$have_req_files = true;
foreach ($this->requiredFiles as $file) {
if (!is_readable($this->path . $file)) {
$have_req_files = false;
- $this->invalidPluginError =
+ $this->errorMsg =
elgg_echo('ElggPluginPackage:InvalidPlugin:MissingFile', array($file));
break;
}
@@ -166,23 +178,21 @@ class ElggPluginPackage {
// check required files
if (!$have_req_files) {
- $valid = false;
+ return $this->valid = false;
}
// check for valid manifest.
if (!$this->loadManifest()) {
- $valid = false;
+ return $this->valid = false;
}
// can't require or conflict with yourself or something you provide.
// make sure provides are all valid.
if (!$this->isSaneDeps()) {
- $valid = false;
+ return $this->valid = false;
}
- $this->valid = $valid;
-
- return $valid;
+ return $this->valid = true;
}
/**
@@ -197,6 +207,12 @@ class ElggPluginPackage {
* @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();
@@ -204,7 +220,7 @@ class ElggPluginPackage {
foreach ($provides as $provide) {
// only valid provide types
if (!in_array($provide['type'], $this->providesSupportedTypes)) {
- $this->invalidPluginError =
+ $this->errorMsg =
elgg_echo('ElggPluginPackage:InvalidPlugin:InvalidProvides', array($provide['type']));
return false;
}
@@ -214,17 +230,17 @@ class ElggPluginPackage {
foreach (array('conflicts', 'requires') as $dep_type) {
foreach (${$dep_type} as $dep) {
if (!in_array($dep['type'], $this->depsSupportedTypes)) {
- $this->invalidPluginError =
+ $this->errorMsg =
elgg_echo('ElggPluginPackage:InvalidPlugin:InvalidDependency', array($dep['type']));
return false;
}
// make sure nothing is providing something it conflicts or requires.
- if ($dep['name'] == $name) {
+ if (isset($dep['name']) && $dep['name'] == $name) {
$version_compare = version_compare($provide['version'], $dep['version'], $dep['comparison']);
if ($version_compare) {
- $this->invalidPluginError =
+ $this->errorMsg =
elgg_echo('ElggPluginPackage:InvalidPlugin:CircularDep',
array($dep['type'], $dep['name'], $this->id));
@@ -238,16 +254,6 @@ class ElggPluginPackage {
return true;
}
- /**
- * Checks if this plugin can be activated on the current
- * Elgg installation.
- *
- * @return bool
- */
- public function canActivate() {
- return $this->checkDependencies();
- }
-
/************
* Manifest *
@@ -260,7 +266,9 @@ class ElggPluginPackage {
*/
public function getManifest() {
if (!$this->manifest) {
- $this->loadManifest();
+ if (!$this->loadManifest()) {
+ return false;
+ }
}
return $this->manifest;
@@ -274,15 +282,34 @@ class ElggPluginPackage {
*/
private function loadManifest() {
$file = $this->path . 'manifest.xml';
- $this->manifest = new ElggPluginManifest($file, $this->id);
- if ($this->manifest) {
+ 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 *
@@ -304,59 +331,110 @@ class ElggPluginPackage {
* @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 = get_installed_plugins('enabled');
+
+ $enabled_plugins = elgg_get_plugins('active');
+ $this_id = $this->getID();
$report = array();
- foreach (array('requires', 'conflicts') as $dep_type) {
+ // 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());
+ $result = $this->checkDepElgg($dep, get_version(), $inverse);
break;
case 'elgg_release':
- $result = $this->checkDepElgg($dep, get_version(true));
+ $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);
+ $result = $this->checkDepPhpExtension($dep, $inverse);
break;
case 'php_ini':
- $result = $this->checkDepPhpIni($dep);
+ $result = $this->checkDepPhpIni($dep, $inverse);
break;
}
// unless we're doing a full report, break as soon as we fail.
- if (!$full_report && !$result) {
- return $result;
+ if (!$full_report && !$result['status']) {
+ $this->errorMsg = "Missing dependencies.";
+ return $result['status'];
} else {
// build report element and comment
- if ($dep_type == 'requires') {
- $comment = '';
- } elseif ($dep_type == 'conflicts') {
- $comment = '';
- }
-
$report[] = array(
'type' => $dep_type,
'dep' => $dep,
- 'status' => $result,
- 'comment' => $comment
+ '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;
}
@@ -367,7 +445,7 @@ class ElggPluginPackage {
* 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 get_installed_plugins();
+ * @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
*/
@@ -375,340 +453,151 @@ class ElggPluginPackage {
$r = elgg_check_plugins_provides('plugin', $dep['name'], $dep['version'], $dep['comparison']);
if ($inverse) {
- $r = !$r;
- }
-
- return $r;
- }
-
- /**
- * 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) {
- $r = version_compare($elgg_version, $dep['version'], $dep['comparison']);
-
- if ($inverse) {
- $r = !$r;
+ $r['status'] = !$r['status'];
}
return $r;
}
/**
- * 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
- * @return bool
- */
- private function checkDepPhpExtension(array $dep) {
- $name = $dep['name'];
- $version = $dep['version'];
- $comparison = $dep['comparison'];
-
- // not enabled.
- $r = extension_loaded($name);
-
- // enabled. check version.
- $ext_version = phpversion($name);
-
- if ($version && !version_compare($ext_version, $version, $comparison)) {
- $r = false;
- }
-
- // some php extensions can be emulated, so check provides.
- if ($r == false) {
- $r = elgg_check_plugins_provides('php_extension', $name, $version, $comparison);
- }
-
- return $r;
- }
-
- /**
- * Check if the PHP ini setting satisfies $dep.
+ * Checks if $plugins meets the requirement by $dep.
*
- * @param array $dep An Elgg manifest.xml deps array
+ * @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 checkDepPhpIni($dep) {
- $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;
+ 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'
+ );
}
- $r = version_compare($setting, $value, $comparison);
-
- return $r;
- }
-
-
- /**************************************
- * Detailed reports for requirements. *
- **************************************/
-
+ $test_plugin_priority = $test_plugin->getPriority();
- /**
- * Returns a report of the dependencies with human
- * readable statuses.
- *
- * @return array
- */
- public function getDependenciesReport() {
- $requires = $this->getManifest()->getRequires();
- $conflicts = $this->getManifest()->getConflicts();
- $enabled_plugins = get_installed_plugins('enabled');
-
- $status = true;
- $messages = array();
-
- $return = array(
- array(
- 'type' => 'requires',
- 'dep' => $dep,
- 'status' => 'bool',
- 'comment' => ''
- )
- );
+ switch ($dep['priority']) {
+ case 'before':
+ $status = $plugin_priority < $test_plugin_priority;
+ break;
- foreach ($requires as $require) {
- switch ($require['type']) {
- case 'elgg_version':
- $result = $this->checkRequiresElgg($require, get_version());
- break;
-
- case 'elgg_release':
- $result = $this->checkRequiresElgg($require, get_version(true));
- break;
-
- case 'plugin':
- $result = $this->checkDepsPlugin($require, $enabled_plugins);
- break;
-
- case 'php_extension':
- $result = $this->checkRequiresPhpExtension($require);
- break;
-
- case 'php_ini':
- $result = $this->checkRequiresPhpIni($require);
- break;
-
- default:
- $result = array(
- 'status' => false,
- 'message' => elgg_echo('ElggPluginPackage:UnknownDep',
- array($require['type'], $this->getManifest()->getPluginID()))
- );
- break;
- }
+ case 'after':
+ $status = $plugin_priority > $test_plugin_priority;
+ break;
- if (!$result['status']) {
+ default;
$status = false;
- $messages[] = $result['message'];
- }
}
- foreach ($conflicts as $conflict) {
-
- }
-
- $return = array(
- 'status' => $status,
- 'messages' => $messages
- );
-
- return $return;
- }
-
- /**
- * Checks if $plugins meets the requirement by $require.
- *
- * Returns an array in the form array('status' => bool, 'message' => 'Any messages')
- *
- * @param array $require An Elgg manifest.xml requires array
- * @param array $plugins A list of plugins as returned by get_installed_plugins();
- * @return array
- */
- private function checkRequiresPlugin(array $require, array $plugins = array()) {
- $status = true;
- $message = '';
-
- $name = $require['name'];
- $version = $require['version'];
- $comparison = $require['comparison'];
-
- // not enabled.
- if (!array_key_exists($name, $plugins)) {
- $status = false;
-
- if ($version) {
- $message = elgg_echo("ElggPluginPackage:Requires:Plugin:NotEnabled:$comparison",
- array($this->getManifest()->getPluginID(), $name, $version));
- } else {
- $message = elgg_echo('ElggPluginPackage:Requires:Plugin:NotEnabled:NoVersion',
- array($this->getManifest()->getPluginID(), $name));
- }
- }
-
- // enabled. check version.
- if ($status != false) {
- $requires_plugin_info = $plugins[$name];
-
- //@todo boot strapping until we can migrate everything over to ElggPluginPackage.
- $plugin_package = new ElggPluginPackage($name);
- $plugin_version = $plugin_package->getManifest()->getVersion();
-
- if ($version && !version_compare($plugin_version, $version, $comparison)) {
- $status = false;
-
- $message = elgg_echo("ElggPluginPackage:Requires:Plugin:$comparison",
- array($this->getManifest()->getPluginID(), $name, $version, $plugin_version));
- }
+ // get the current value
+ if ($plugin_priority < $test_plugin_priority) {
+ $value = 'before';
+ } else {
+ $value = 'after';
}
- // if all else fails check with the provides
- if ($status == false) {
- if (elgg_check_plugins_provides('plugin', $name)) {
- // it's provided. check version if asked.
- $status = true;
- $message = '';
-
- if ($version && !elgg_check_plugins_provides('plugin', $name, $version, $comparison)) {
- // change the message to something more meaningful
- $provide = elgg_get_plugins_provides('plugin', $name);
- $plugin_version = "{$provide['provided_by']}:$name={$provide['version']}";
-
- $status = false;
- $message = elgg_echo("ElggPluginPackage:Requires:Plugin:$comparison",
- array($this->getManifest()->getPluginID(), $name, $version, $plugin_version));
- }
- }
+ if ($inverse) {
+ $status = !$status;
}
return array(
'status' => $status,
- 'message' => $message
+ 'value' => $value
);
}
/**
- * Checks if $elgg_version meets the requirement by $require.
- *
- * Returns an array in the form array('status' => bool, 'message' => 'Any messages')
+ * Checks if $elgg_version meets the requirement by $dep.
*
- * @param array $require An Elgg manifest.xml requires array
+ * @param array $dep An Elgg manifest.xml deps array
* @param array $elgg_version An Elgg version (either YYYYMMDDXX or X.Y.Z)
- * @return array
+ * @param bool $inverse Inverse the result to use as a conflicts.
+ * @return bool
*/
- private function checkRequiresElgg(array $require, $elgg_version) {
- $status = true;
- $message = '';
- $version = $require['version'];
- $comparison = $require['comparison'];
-
- if (!version_compare($elgg_version, $version, $comparison)) {
- $status = false;
- $message = elgg_echo("ElggPluginPackage:Requires:Elgg:$comparison",
- array($this->getManifest()->getPluginID(), $version));
+ 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,
- 'message' => $message
+ 'value' => $elgg_version
);
}
/**
- * Checks if the PHP extension in $require is loaded.
+ * Checks if the PHP extension in $dep is loaded.
*
* @todo Can this be merged with the plugin checker?
*
- * @param array $require An Elgg manifest.xml deps array
- * @return array
+ * @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 checkRequiresPhpExtension($require) {
- $status = true;
- $message = '';
-
- $name = $require['name'];
- $version = $require['version'];
- $comparison = $require['comparison'];
+ private function checkDepPhpExtension(array $dep, $inverse = false) {
+ $name = $dep['name'];
+ $version = $dep['version'];
+ $comparison = $dep['comparison'];
// not enabled.
- if (!extension_loaded($name)) {
- $status = false;
- if ($version) {
- $message = elgg_echo("ElggPluginPackage:Requires:PhpExtension:NotInstalled:$comparison",
- array($this->getManifest()->getPluginID(), $name, $version));
- } else {
- $message = elgg_echo('ElggPluginPackage:Requires:PhpExtension:NotInstalled:NoVersion',
- array($this->getManifest()->getPluginID(), $name));
- }
- }
+ $status = extension_loaded($name);
// enabled. check version.
- if ($status != false) {
- $ext_version = phpversion($name);
+ $ext_version = phpversion($name);
- if ($version && !version_compare($ext_version, $version, $comparison)) {
- $status = false;
- $message = elgg_echo("ElggPluginPackage:Requires:PhpExtension:$comparison",
- array($this->getManifest()->getPluginID(), $name, $version));
+ 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) {
- if (elgg_check_plugins_provides('php_extension', $name)) {
- // it's provided. check version if asked.
- $status = true;
- $message = '';
-
- if ($version && !elgg_check_plugins_provides('php_extension', $name, $version, $comparison)) {
- // change the message to something more meaningful
- $provide = elgg_get_plugins_provides('php_extension', $name);
- $plugin_version = "{$provide['provided_by']}:$name={$provide['version']}";
-
- $status = false;
- $message = elgg_echo("ElggPluginPackage:Requires:PhpExtension:$comparison",
- array($this->getManifest()->getPluginID(), $name, $version, $plugin_version));
- }
- }
+ $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,
- 'message' => $message
+ 'value' => $ext_version
);
}
-
/**
- * Check if the PHP ini setting satisfies $require.
+ * Check if the PHP ini setting satisfies $dep.
*
- * @param array $require An Elgg manifest.xml requires array
- * @return array
+ * @param array $dep An Elgg manifest.xml deps array
+ * @param bool $inverse Inverse the result to use as a conflicts.
+ * @return bool
*/
- private function checkRequiresPhpIni($require) {
- $status = true;
- $message = '';
-
- $name = $require['name'];
- $value = $require['value'];
- $comparison = $require['comparison'];
+ 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.
@@ -719,41 +608,19 @@ class ElggPluginPackage {
$setting = 0;
}
- if (!version_compare($setting, $value, $comparison)) {
- $status = false;
- $message = elgg_echo("ElggPluginPackage:Requires:PhpIni:$comparison",
- array($this->getManifest()->getPluginID(), $name, $value, $setting));
+ $status = version_compare($setting, $value, $comparison);
+
+ if ($inverse) {
+ $status = !$status;
}
return array(
'status' => $status,
- 'message' => $message
+ 'value' => $setting
);
}
/**
- * Activate the plugin.
- *
- * @note This method is activate() to avoid clashing with ElggEntity::enable()
- *
- * @return bool
- */
- public function activate() {
- return enable_plugin($this->getID());
- }
-
- /**
- * Deactivate the plugin.
- *
- * @note This method is deactivate() to avoid clashing with ElggEntity::disable()
- *
- * @return bool
- */
- public function deactivate() {
- return disable_plugin($this->getID());
- }
-
- /**
* Returns the Plugin ID
*
* @return string
@@ -762,4 +629,12 @@ class ElggPluginPackage {
return $this->id;
}
-} \ No newline at end of file
+ /**
+ * 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 50a86fff1..d2e88882a 100644
--- a/engine/classes/ElggRelationship.php
+++ b/engine/classes/ElggRelationship.php
@@ -4,15 +4,21 @@
*
* @package Elgg.Core
* @subpackage Core
+ *
+ * @property int $id The unique identifier (read-only)
+ * @property int $guid_one The GUID of the subject of the relationship
+ * @property string $relationship The name of the relationship
+ * @property int $guid_two The GUID of the object of the relationship
+ * @property int $time_created A UNIX timestamp of when the relationship was created (read-only, set on first save)
*/
class ElggRelationship extends ElggData implements
Importable
{
/**
- * Construct a new site object, optionally from a given id value or row.
+ * Create a relationship object, optionally from a given id value or row.
*
- * @param mixed $id ElggRelationship id
+ * @param mixed $id ElggRelationship id, database row, or null for new relationship
*/
function __construct($id = null) {
$this->initializeAttributes();
@@ -41,7 +47,7 @@ class ElggRelationship extends ElggData implements
* @return mixed
*/
function get($name) {
- if (isset($this->attributes[$name])) {
+ if (array_key_exists($name, $this->attributes)) {
return $this->attributes[$name];
}
@@ -65,6 +71,7 @@ class ElggRelationship extends ElggData implements
* Save the relationship
*
* @return int the relationship id
+ * @throws IOException
*/
public function save() {
if ($this->id > 0) {
@@ -136,14 +143,13 @@ class ElggRelationship extends ElggData implements
/**
* Import a relationship
*
- * @param array $data ODD data
- *
- * @return ElggRelationship
- *
- * @throws ImportException
+ * @param ODD $data ODD data
+
+ * @return bool
+ * @throws ImportException|InvalidParameterException
*/
public function import(ODD $data) {
- if (!($element instanceof ODDRelationship)) {
+ if (!($data instanceof ODDRelationship)) {
throw new InvalidParameterException(elgg_echo('InvalidParameterException:UnexpectedODDClass'));
}
@@ -171,9 +177,11 @@ class ElggRelationship extends ElggData implements
throw new ImportException(elgg_echo('ImportException:ProblemSaving', array(get_class())));
}
- return $this;
+ return true;
}
}
+
+ return false;
}
// SYSTEM LOG INTERFACE ////////////////////////////////////////////////////////////
@@ -220,4 +228,4 @@ class ElggRelationship extends ElggData implements
return $this->relationship;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggRiverItem.php b/engine/classes/ElggRiverItem.php
new file mode 100644
index 000000000..d3d09cd91
--- /dev/null
+++ b/engine/classes/ElggRiverItem.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * River item class.
+ *
+ * @package Elgg.Core
+ * @subpackage Core
+ *
+ * @property int $id The unique identifier (read-only)
+ * @property int $subject_guid The GUID of the actor
+ * @property int $object_guid The GUID of the object
+ * @property int $annotation_id The ID of the annotation involved in the action
+ * @property string $type The type of one of the entities involved in the action
+ * @property string $subtype The subtype of one of the entities involved in the action
+ * @property string $action_type The name of the action
+ * @property string $view The view for displaying this river item
+ * @property int $access_id The visibility of the river item
+ * @property int $posted UNIX timestamp when the action occurred
+ */
+class ElggRiverItem {
+ public $id;
+ public $subject_guid;
+ public $object_guid;
+ public $annotation_id;
+ public $type;
+ public $subtype;
+ public $action_type;
+ public $access_id;
+ public $view;
+ public $posted;
+
+ /**
+ * Construct a river item object given a database row.
+ *
+ * @param stdClass $object Object obtained from database
+ */
+ function __construct($object) {
+ if (!($object instanceof stdClass)) {
+ // throw exception
+ }
+
+ // the casting is to support typed serialization like json
+ $int_types = array('id', 'subject_guid', 'object_guid', 'annotation_id', 'access_id', 'posted');
+ foreach ($object as $key => $value) {
+ if (in_array($key, $int_types)) {
+ $this->$key = (int)$value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * Get the subject of this river item
+ *
+ * @return ElggEntity
+ */
+ public function getSubjectEntity() {
+ return get_entity($this->subject_guid);
+ }
+
+ /**
+ * Get the object of this river item
+ *
+ * @return ElggEntity
+ */
+ public function getObjectEntity() {
+ return get_entity($this->object_guid);
+ }
+
+ /**
+ * Get the Annotation for this river item
+ *
+ * @return ElggAnnotation
+ */
+ public function getAnnotation() {
+ return elgg_get_annotation_from_id($this->annotation_id);
+ }
+
+ /**
+ * Get the view used to display this river item
+ *
+ * @return string
+ */
+ public function getView() {
+ return $this->view;
+ }
+
+ /**
+ * Get the time this activity was posted
+ *
+ * @return int
+ */
+ public function getPostedTime() {
+ return (int)$this->posted;
+ }
+
+ /**
+ * Get the type of the object
+ *
+ * @return string 'river'
+ */
+ public function getType() {
+ return 'river';
+ }
+
+ /**
+ * Get the subtype of the object
+ *
+ * @return string 'item'
+ */
+ public function getSubtype() {
+ return 'item';
+ }
+
+}
diff --git a/engine/classes/ElggSession.php b/engine/classes/ElggSession.php
index 8a3bed84a..9750f063e 100644
--- a/engine/classes/ElggSession.php
+++ b/engine/classes/ElggSession.php
@@ -54,7 +54,7 @@ class ElggSession implements ArrayAccess {
*
* @param mixed $key Name
*
- * @return void
+ * @return mixed
*/
function offsetGet($key) {
if (!ElggSession::$__localcache) {
@@ -98,7 +98,7 @@ class ElggSession implements ArrayAccess {
*
* @param int $offset Offset
*
- * @return int
+ * @return bool
*/
function offsetExists($offset) {
if (isset(ElggSession::$__localcache[$offset])) {
@@ -112,6 +112,8 @@ class ElggSession implements ArrayAccess {
if ($this->offsetGet($offset)) {
return true;
}
+
+ return false;
}
@@ -132,10 +134,10 @@ class ElggSession implements ArrayAccess {
* @param string $key Name
* @param mixed $value Value
*
- * @return mixed
+ * @return void
*/
function set($key, $value) {
- return $this->offsetSet($key, $value);
+ $this->offsetSet($key, $value);
}
/**
@@ -143,9 +145,9 @@ class ElggSession implements ArrayAccess {
*
* @param string $key Name
*
- * @return bool
+ * @return void
*/
function del($key) {
- return $this->offsetUnset($key);
+ $this->offsetUnset($key);
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggSharedMemoryCache.php b/engine/classes/ElggSharedMemoryCache.php
index 6d6a3d625..f5f11d2c7 100644
--- a/engine/classes/ElggSharedMemoryCache.php
+++ b/engine/classes/ElggSharedMemoryCache.php
@@ -37,4 +37,4 @@ abstract class ElggSharedMemoryCache extends ElggCache {
public function getNamespace() {
return $this->namespace;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggSite.php b/engine/classes/ElggSite.php
index 68a720bfe..dd996fe98 100644
--- a/engine/classes/ElggSite.php
+++ b/engine/classes/ElggSite.php
@@ -21,6 +21,10 @@
* @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 {
@@ -53,8 +57,8 @@ class ElggSite extends ElggEntity {
* - A URL as stored in ElggSite->url
* - A DB result object with a guid property
*
- * @param mixed $guid If an int, load that GUID. If a db row then will attempt
- * to load the rest of the data.
+ * @param mixed $guid If an int, load that GUID. If a db row then will
+ * load the rest of the data.
*
* @throws IOException If passed an incorrect guid
* @throws InvalidParameterException If passed an Elgg* Entity that isn't an ElggSite
@@ -66,35 +70,31 @@ class ElggSite extends ElggEntity {
$this->initialise_attributes(false);
if (!empty($guid)) {
- // Is $guid is a DB row - either a entity row, or a site table row.
+ // Is $guid is a DB entity table row
if ($guid instanceof stdClass) {
// Load the rest
- if (!$this->load($guid->guid)) {
+ if (!$this->load($guid)) {
$msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid));
throw new IOException($msg);
}
-
- // Is $guid is an ElggSite? Use a copy constructor
} else if ($guid instanceof ElggSite) {
+ // $guid is an ElggSite so this is a copy constructor
elgg_deprecated_notice('This type of usage of the ElggSite constructor was deprecated. Please use the clone method.', 1.7);
foreach ($guid->attributes as $key => $value) {
$this->attributes[$key] = $value;
}
-
- // Is this is an ElggEntity but not an ElggSite = ERROR!
} else if ($guid instanceof ElggEntity) {
+ // @todo remove and just use else clause
throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggSite'));
-
- // See if this is a URL
} else if (strpos($guid, "http") !== false) {
+ // url so retrieve by url
$guid = get_site_by_url($guid);
foreach ($guid->attributes as $key => $value) {
$this->attributes[$key] = $value;
}
-
- // We assume if we have got this far, $guid is an int
} else if (is_numeric($guid)) {
+ // $guid is a GUID so load
if (!$this->load($guid)) {
throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid)));
}
@@ -107,35 +107,24 @@ class ElggSite extends ElggEntity {
/**
* Loads the full ElggSite when given a guid.
*
- * @param int $guid Guid of ElggSite entity
+ * @param mixed $guid GUID of ElggSite entity or database row object
*
* @return bool
* @throws InvalidClassException
*/
protected function load($guid) {
- // Test to see if we have the generic stuff
- if (!parent::load($guid)) {
- return false;
- }
+ $attr_loader = new ElggAttributeLoader(get_class(), 'site', $this->attributes);
+ $attr_loader->requires_access_control = !($this instanceof ElggPlugin);
+ $attr_loader->secondary_loader = 'get_site_entity_as_row';
- // Check the type
- if ($this->attributes['type'] != 'site') {
- $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class()));
- throw new InvalidClassException($msg);
- }
-
- // Load missing data
- $row = get_site_entity_as_row($guid);
- if (($row) && (!$this->isFullyLoaded())) {
- // If $row isn't a cached copy then increment the counter
- $this->attributes['tables_loaded'] ++;
+ $attrs = $attr_loader->getRequiredAttributes($guid);
+ if (!$attrs) {
+ return false;
}
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach ($objarray as $key => $value) {
- $this->attributes[$key] = $value;
- }
+ $this->attributes = $attrs;
+ $this->attributes['tables_loaded'] = 2;
+ _elgg_cache_entity($this);
return true;
}
@@ -148,11 +137,20 @@ class ElggSite extends ElggEntity {
* @return bool
*/
public function save() {
+ global $CONFIG;
+
// Save generic stuff
if (!parent::save()) {
return false;
}
+ // make sure the site guid is set (if not, set to self)
+ if (!$this->get('site_guid')) {
+ $guid = $this->get('guid');
+ update_data("UPDATE {$CONFIG->dbprefix}entities SET site_guid=$guid
+ WHERE guid=$guid");
+ }
+
// Now save specific stuff
return create_site_entity($this->get('guid'), $this->get('name'),
$this->get('description'), $this->get('url'));
@@ -180,19 +178,20 @@ class ElggSite extends ElggEntity {
*
* @note You cannot disable the current site.
*
- * @param string $reason Optional reason for disabling
+ * @param string $reason Optional reason for disabling
+ * @param bool $recursive Recursively disable all contained entities?
*
* @return bool
* @throws SecurityException
*/
- public function disable($reason = "") {
+ public function disable($reason = "", $recursive = true) {
global $CONFIG;
if ($CONFIG->site->getGUID() == $this->guid) {
throw new SecurityException('SecurityException:deletedisablecurrentsite');
}
- return parent::disable($reason);
+ return parent::disable($reason, $recursive);
}
/**
@@ -202,22 +201,23 @@ class ElggSite extends ElggEntity {
* accepted by elgg_get_entities(). Common parameters
* include 'limit', and 'offset'.
* Note: this was $limit before version 1.8
- * @param int $offset Offset @deprecated parameter
+ * @param int $offset Offset @deprecated parameter
*
* @todo remove $offset in 2.0
*
* @return array of ElggUsers
*/
public function getMembers($options = array(), $offset = 0) {
- if (!is_array($limit)) {
- elgg_deprecated_notice("ElggSite::getMembers has a different arguments!", 1.8);
+ 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,
@@ -241,6 +241,7 @@ class ElggSite extends ElggEntity {
*/
public function listMembers($options = array()) {
$defaults = array(
+ 'site_guids' => ELGG_ENTITIES_ANY_VALUE,
'relationship' => 'member_of_site',
'relationship_guid' => $this->getGUID(),
'inverse_relationship' => TRUE,
@@ -357,13 +358,31 @@ class ElggSite extends ElggEntity {
public function checkWalledGarden() {
global $CONFIG;
- if ($CONFIG->walled_garden && !isloggedin()) {
- // hook into the index system call at the highest priority
- elgg_register_plugin_hook_handler('index', 'system', 'elgg_walled_garden_index', 1);
+ // command line calls should not invoke the walled garden check
+ if (PHP_SAPI === 'cli') {
+ return;
+ }
- if (!$this->isPublicPage()) {
- register_error(elgg_echo('loggedinrequired'));
- forward();
+ if ($CONFIG->walled_garden) {
+ if ($CONFIG->default_access == ACCESS_PUBLIC) {
+ $CONFIG->default_access = ACCESS_LOGGED_IN;
+ }
+ elgg_register_plugin_hook_handler(
+ 'access:collections:write',
+ 'user',
+ '_elgg_walled_garden_remove_public_access');
+
+ if (!elgg_is_logged_in()) {
+ // hook into the index system call at the highest priority
+ elgg_register_plugin_hook_handler('index', 'system', 'elgg_walled_garden_index', 1);
+
+ if (!$this->isPublicPage()) {
+ if (!elgg_is_xhr()) {
+ $_SESSION['last_forward_from'] = current_page_url();
+ }
+ register_error(elgg_echo('loggedinrequired'));
+ forward();
+ }
}
}
}
@@ -397,24 +416,31 @@ class ElggSite extends ElggEntity {
// default public pages
$defaults = array(
+ 'walled_garden/.*',
+ 'login',
'action/login',
- 'pg/register',
+ 'register',
'action/register',
- 'pages/account/forgotten_password\.php',
+ 'forgotpassword',
+ 'resetpassword',
'action/user/requestnewpassword',
- 'pg/resetpassword',
+ 'action/user/passwordreset',
+ 'action/security/refreshtoken',
+ 'ajax/view/js/languages',
'upgrade\.php',
'xml-rpc\.php',
'mt/mt-xmlrpc\.cgi',
- '_css/css\.css',
- '_css/js\.php',
+ 'css/.*',
+ 'js/.*',
+ 'cache/css/.*',
+ 'cache/js/.*',
+ 'cron/.*',
+ 'services/.*',
);
// include a hook for plugin authors to include public pages
$plugins = elgg_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";
diff --git a/engine/classes/ElggStaticVariableCache.php b/engine/classes/ElggStaticVariableCache.php
index a846ab60f..9c14fdfba 100644
--- a/engine/classes/ElggStaticVariableCache.php
+++ b/engine/classes/ElggStaticVariableCache.php
@@ -11,7 +11,7 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache {
/**
* The cache.
*
- * @var unknown_type
+ * @var array
*/
private static $__cache;
@@ -21,8 +21,8 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache {
* This function creates a variable cache in a static variable in
* memory, optionally with a given namespace (to avoid overlap).
*
- * @param string $namespace The namespace for this cache to write to
- * note, namespaces of the same name are shared!
+ * @param string $namespace The namespace for this cache to write to.
+ * @warning namespaces of the same name are shared!
*/
function __construct($namespace = 'default') {
$this->setNamespace($namespace);
@@ -80,7 +80,7 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache {
}
/**
- * This was probably meant to delete everything?
+ * Clears the cache for a particular namespace
*
* @return void
*/
@@ -93,4 +93,4 @@ class ElggStaticVariableCache extends ElggSharedMemoryCache {
ElggStaticVariableCache::$__cache[$namespace] = array();
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggTranslit.php b/engine/classes/ElggTranslit.php
new file mode 100644
index 000000000..b4bf87797
--- /dev/null
+++ b/engine/classes/ElggTranslit.php
@@ -0,0 +1,269 @@
+<?php
+/**
+ * Elgg Transliterate
+ *
+ * For creating "friendly titles" for URLs
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ *
+ * @package Elgg.Core
+ * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ * @author Steve Clay <steve@mrclay.org>
+ *
+ * @access private Plugin authors should not use this directly
+ */
+class ElggTranslit {
+
+ /**
+ * Create a version of a string for embedding in a URL
+ *
+ * @param string $string A UTF-8 string
+ * @param string $separator The character to separate words with
+ * @return string
+ */
+ static public function urlize($string, $separator = '-') {
+ // Iñtërnâtiônàlizætiøn, AND 日本語!
+
+ // try to force combined chars because the translit map and others expect it
+ if (self::hasNormalizerSupport()) {
+ $nfc = normalizer_normalize($string);
+ if (is_string($nfc)) {
+ $string = $nfc;
+ }
+ }
+ // Internationalization, AND 日本語!
+ $string = self::transliterateAscii($string);
+
+ // allow HTML tags in titles
+ $string = preg_replace('~<([a-zA-Z][^>]*)>~', ' $1 ', $string);
+
+ // more substitutions
+ // @todo put these somewhere else
+ $string = strtr($string, array(
+ // currency
+ "\xE2\x82\xAC" /* € */ => ' E ',
+ "\xC2\xA3" /* £ */ => ' GBP ',
+ ));
+
+ // remove all ASCII except 0-9a-zA-Z, hyphen, underscore, and whitespace
+ // note: "x" modifier did not work with this pattern.
+ $string = preg_replace('~['
+ . '\x00-\x08' // control chars
+ . '\x0b\x0c' // vert tab, form feed
+ . '\x0e-\x1f' // control chars
+ . '\x21-\x2c' // ! ... ,
+ . '\x2e\x2f' // . slash
+ . '\x3a-\x40' // : ... @
+ . '\x5b-\x5e' // [ ... ^
+ . '\x60' // `
+ . '\x7b-\x7f' // { ... DEL
+ . ']~', '', $string);
+ $string = strtr($string, '', '');
+
+ // internationalization, and 日本語!
+ // note: not using elgg_strtolower to keep this class portable
+ $string = is_callable('mb_strtolower')
+ ? mb_strtolower($string, 'UTF-8')
+ : strtolower($string);
+
+ // split by ASCII chars not in 0-9a-zA-Z
+ // note: we cannot use [^0-9a-zA-Z] because that matches multibyte chars.
+ // note: "x" modifier did not work with this pattern.
+ $pattern = '~['
+ . '\x00-\x2f' // controls ... slash
+ . '\x3a-\x40' // : ... @
+ . '\x5b-\x60' // [ ... `
+ . '\x7b-\x7f' // { ... DEL
+ . ']+~x';
+
+ // ['internationalization', 'and', '日本語']
+ $words = preg_split($pattern, $string, -1, PREG_SPLIT_NO_EMPTY);
+
+ // ['internationalization', 'and', '%E6%97%A5%E6%9C%AC%E8%AA%9E']
+ $words = array_map('urlencode', $words);
+
+ // internationalization-and-%E6%97%A5%E6%9C%AC%E8%AA%9E
+ return implode($separator, $words);
+ }
+
+ /**
+ * Transliterate Western multibyte chars to ASCII
+ *
+ * @param string $utf8 a UTF-8 string
+ * @return string
+ */
+ static public function transliterateAscii($utf8) {
+ static $map = null;
+ if (!preg_match('/[\x80-\xff]/', $utf8)) {
+ return $utf8;
+ }
+ if (null === $map) {
+ $map = self::getAsciiTranslitMap();
+ }
+ return strtr($utf8, $map);
+ }
+
+ /**
+ * Get array of UTF-8 (NFC) character replacements.
+ *
+ * @return array
+ */
+ static public function getAsciiTranslitMap() {
+ return array(
+ // Decompositions for Latin-1 Supplement
+ "\xC2\xAA" /* ª */ => 'a', "\xC2\xBA" /* º */ => 'o', "\xC3\x80" /* À */ => 'A',
+ "\xC3\x81" /* Á */ => 'A', "\xC3\x82" /* Â */ => 'A', "\xC3\x83" /* Ã */ => 'A',
+ "\xC3\x84" /* Ä */ => 'A', "\xC3\x85" /* Å */ => 'A', "\xC3\x86" /* Æ */ => 'AE',
+ "\xC3\x87" /* Ç */ => 'C', "\xC3\x88" /* È */ => 'E', "\xC3\x89" /* É */ => 'E',
+ "\xC3\x8A" /* Ê */ => 'E', "\xC3\x8B" /* Ë */ => 'E', "\xC3\x8C" /* Ì */ => 'I',
+ "\xC3\x8D" /* Í */ => 'I', "\xC3\x8E" /* Î */ => 'I', "\xC3\x8F" /* Ï */ => 'I',
+ "\xC3\x90" /* Ð */ => 'D', "\xC3\x91" /* Ñ */ => 'N', "\xC3\x92" /* Ò */ => 'O',
+ "\xC3\x93" /* Ó */ => 'O', "\xC3\x94" /* Ô */ => 'O', "\xC3\x95" /* Õ */ => 'O',
+ "\xC3\x96" /* Ö */ => 'O', "\xC3\x99" /* Ù */ => 'U', "\xC3\x9A" /* Ú */ => 'U',
+ "\xC3\x9B" /* Û */ => 'U', "\xC3\x9C" /* Ü */ => 'U', "\xC3\x9D" /* Ý */ => 'Y',
+ "\xC3\x9E" /* Þ */ => 'TH', "\xC3\x9F" /* ß */ => 'ss', "\xC3\xA0" /* à */ => 'a',
+ "\xC3\xA1" /* á */ => 'a', "\xC3\xA2" /* â */ => 'a', "\xC3\xA3" /* ã */ => 'a',
+ "\xC3\xA4" /* ä */ => 'a', "\xC3\xA5" /* å */ => 'a', "\xC3\xA6" /* æ */ => 'ae',
+ "\xC3\xA7" /* ç */ => 'c', "\xC3\xA8" /* è */ => 'e', "\xC3\xA9" /* é */ => 'e',
+ "\xC3\xAA" /* ê */ => 'e', "\xC3\xAB" /* ë */ => 'e', "\xC3\xAC" /* ì */ => 'i',
+ "\xC3\xAD" /* í */ => 'i', "\xC3\xAE" /* î */ => 'i', "\xC3\xAF" /* ï */ => 'i',
+ "\xC3\xB0" /* ð */ => 'd', "\xC3\xB1" /* ñ */ => 'n', "\xC3\xB2" /* ò */ => 'o',
+ "\xC3\xB3" /* ó */ => 'o', "\xC3\xB4" /* ô */ => 'o', "\xC3\xB5" /* õ */ => 'o',
+ "\xC3\xB6" /* ö */ => 'o', "\xC3\xB8" /* ø */ => 'o', "\xC3\xB9" /* ù */ => 'u',
+ "\xC3\xBA" /* ú */ => 'u', "\xC3\xBB" /* û */ => 'u', "\xC3\xBC" /* ü */ => 'u',
+ "\xC3\xBD" /* ý */ => 'y', "\xC3\xBE" /* þ */ => 'th', "\xC3\xBF" /* ÿ */ => 'y',
+ "\xC3\x98" /* Ø */ => 'O',
+ // Decompositions for Latin Extended-A
+ "\xC4\x80" /* Ā */ => 'A', "\xC4\x81" /* ā */ => 'a', "\xC4\x82" /* Ă */ => 'A',
+ "\xC4\x83" /* ă */ => 'a', "\xC4\x84" /* Ą */ => 'A', "\xC4\x85" /* ą */ => 'a',
+ "\xC4\x86" /* Ć */ => 'C', "\xC4\x87" /* ć */ => 'c', "\xC4\x88" /* Ĉ */ => 'C',
+ "\xC4\x89" /* ĉ */ => 'c', "\xC4\x8A" /* Ċ */ => 'C', "\xC4\x8B" /* ċ */ => 'c',
+ "\xC4\x8C" /* Č */ => 'C', "\xC4\x8D" /* č */ => 'c', "\xC4\x8E" /* Ď */ => 'D',
+ "\xC4\x8F" /* ď */ => 'd', "\xC4\x90" /* Đ */ => 'D', "\xC4\x91" /* đ */ => 'd',
+ "\xC4\x92" /* Ē */ => 'E', "\xC4\x93" /* ē */ => 'e', "\xC4\x94" /* Ĕ */ => 'E',
+ "\xC4\x95" /* ĕ */ => 'e', "\xC4\x96" /* Ė */ => 'E', "\xC4\x97" /* ė */ => 'e',
+ "\xC4\x98" /* Ę */ => 'E', "\xC4\x99" /* ę */ => 'e', "\xC4\x9A" /* Ě */ => 'E',
+ "\xC4\x9B" /* ě */ => 'e', "\xC4\x9C" /* Ĝ */ => 'G', "\xC4\x9D" /* ĝ */ => 'g',
+ "\xC4\x9E" /* Ğ */ => 'G', "\xC4\x9F" /* ğ */ => 'g', "\xC4\xA0" /* Ġ */ => 'G',
+ "\xC4\xA1" /* ġ */ => 'g', "\xC4\xA2" /* Ģ */ => 'G', "\xC4\xA3" /* ģ */ => 'g',
+ "\xC4\xA4" /* Ĥ */ => 'H', "\xC4\xA5" /* ĥ */ => 'h', "\xC4\xA6" /* Ħ */ => 'H',
+ "\xC4\xA7" /* ħ */ => 'h', "\xC4\xA8" /* Ĩ */ => 'I', "\xC4\xA9" /* ĩ */ => 'i',
+ "\xC4\xAA" /* Ī */ => 'I', "\xC4\xAB" /* ī */ => 'i', "\xC4\xAC" /* Ĭ */ => 'I',
+ "\xC4\xAD" /* ĭ */ => 'i', "\xC4\xAE" /* Į */ => 'I', "\xC4\xAF" /* į */ => 'i',
+ "\xC4\xB0" /* İ */ => 'I', "\xC4\xB1" /* ı */ => 'i', "\xC4\xB2" /* IJ */ => 'IJ',
+ "\xC4\xB3" /* ij */ => 'ij', "\xC4\xB4" /* Ĵ */ => 'J', "\xC4\xB5" /* ĵ */ => 'j',
+ "\xC4\xB6" /* Ķ */ => 'K', "\xC4\xB7" /* ķ */ => 'k', "\xC4\xB8" /* ĸ */ => 'k',
+ "\xC4\xB9" /* Ĺ */ => 'L', "\xC4\xBA" /* ĺ */ => 'l', "\xC4\xBB" /* Ļ */ => 'L',
+ "\xC4\xBC" /* ļ */ => 'l', "\xC4\xBD" /* Ľ */ => 'L', "\xC4\xBE" /* ľ */ => 'l',
+ "\xC4\xBF" /* Ŀ */ => 'L', "\xC5\x80" /* ŀ */ => 'l', "\xC5\x81" /* Ł */ => 'L',
+ "\xC5\x82" /* ł */ => 'l', "\xC5\x83" /* Ń */ => 'N', "\xC5\x84" /* ń */ => 'n',
+ "\xC5\x85" /* Ņ */ => 'N', "\xC5\x86" /* ņ */ => 'n', "\xC5\x87" /* Ň */ => 'N',
+ "\xC5\x88" /* ň */ => 'n', "\xC5\x89" /* ʼn */ => 'N', "\xC5\x8A" /* Ŋ */ => 'n',
+ "\xC5\x8B" /* ŋ */ => 'N', "\xC5\x8C" /* Ō */ => 'O', "\xC5\x8D" /* ō */ => 'o',
+ "\xC5\x8E" /* Ŏ */ => 'O', "\xC5\x8F" /* ŏ */ => 'o', "\xC5\x90" /* Ő */ => 'O',
+ "\xC5\x91" /* ő */ => 'o', "\xC5\x92" /* Œ */ => 'OE', "\xC5\x93" /* œ */ => 'oe',
+ "\xC5\x94" /* Ŕ */ => 'R', "\xC5\x95" /* ŕ */ => 'r', "\xC5\x96" /* Ŗ */ => 'R',
+ "\xC5\x97" /* ŗ */ => 'r', "\xC5\x98" /* Ř */ => 'R', "\xC5\x99" /* ř */ => 'r',
+ "\xC5\x9A" /* Ś */ => 'S', "\xC5\x9B" /* ś */ => 's', "\xC5\x9C" /* Ŝ */ => 'S',
+ "\xC5\x9D" /* ŝ */ => 's', "\xC5\x9E" /* Ş */ => 'S', "\xC5\x9F" /* ş */ => 's',
+ "\xC5\xA0" /* Š */ => 'S', "\xC5\xA1" /* š */ => 's', "\xC5\xA2" /* Ţ */ => 'T',
+ "\xC5\xA3" /* ţ */ => 't', "\xC5\xA4" /* Ť */ => 'T', "\xC5\xA5" /* ť */ => 't',
+ "\xC5\xA6" /* Ŧ */ => 'T', "\xC5\xA7" /* ŧ */ => 't', "\xC5\xA8" /* Ũ */ => 'U',
+ "\xC5\xA9" /* ũ */ => 'u', "\xC5\xAA" /* Ū */ => 'U', "\xC5\xAB" /* ū */ => 'u',
+ "\xC5\xAC" /* Ŭ */ => 'U', "\xC5\xAD" /* ŭ */ => 'u', "\xC5\xAE" /* Ů */ => 'U',
+ "\xC5\xAF" /* ů */ => 'u', "\xC5\xB0" /* Ű */ => 'U', "\xC5\xB1" /* ű */ => 'u',
+ "\xC5\xB2" /* Ų */ => 'U', "\xC5\xB3" /* ų */ => 'u', "\xC5\xB4" /* Ŵ */ => 'W',
+ "\xC5\xB5" /* ŵ */ => 'w', "\xC5\xB6" /* Ŷ */ => 'Y', "\xC5\xB7" /* ŷ */ => 'y',
+ "\xC5\xB8" /* Ÿ */ => 'Y', "\xC5\xB9" /* Ź */ => 'Z', "\xC5\xBA" /* ź */ => 'z',
+ "\xC5\xBB" /* Ż */ => 'Z', "\xC5\xBC" /* ż */ => 'z', "\xC5\xBD" /* Ž */ => 'Z',
+ "\xC5\xBE" /* ž */ => 'z', "\xC5\xBF" /* ſ */ => 's',
+ // Decompositions for Latin Extended-B
+ "\xC8\x98" /* Ș */ => 'S', "\xC8\x99" /* ș */ => 's',
+ "\xC8\x9A" /* Ț */ => 'T', "\xC8\x9B" /* ț */ => 't',
+ // unmarked
+ "\xC6\xA0" /* Ơ */ => 'O', "\xC6\xA1" /* ơ */ => 'o',
+ "\xC6\xAF" /* Ư */ => 'U', "\xC6\xB0" /* ư */ => 'u',
+ // grave accent
+ "\xE1\xBA\xA6" /* Ầ */ => 'A', "\xE1\xBA\xA7" /* ầ */ => 'a',
+ "\xE1\xBA\xB0" /* Ằ */ => 'A', "\xE1\xBA\xB1" /* ằ */ => 'a',
+ "\xE1\xBB\x80" /* Ề */ => 'E', "\xE1\xBB\x81" /* ề */ => 'e',
+ "\xE1\xBB\x92" /* Ồ */ => 'O', "\xE1\xBB\x93" /* ồ */ => 'o',
+ "\xE1\xBB\x9C" /* Ờ */ => 'O', "\xE1\xBB\x9D" /* ờ */ => 'o',
+ "\xE1\xBB\xAA" /* Ừ */ => 'U', "\xE1\xBB\xAB" /* ừ */ => 'u',
+ "\xE1\xBB\xB2" /* Ỳ */ => 'Y', "\xE1\xBB\xB3" /* ỳ */ => 'y',
+ // hook
+ "\xE1\xBA\xA2" /* Ả */ => 'A', "\xE1\xBA\xA3" /* ả */ => 'a',
+ "\xE1\xBA\xA8" /* Ẩ */ => 'A', "\xE1\xBA\xA9" /* ẩ */ => 'a',
+ "\xE1\xBA\xB2" /* Ẳ */ => 'A', "\xE1\xBA\xB3" /* ẳ */ => 'a',
+ "\xE1\xBA\xBA" /* Ẻ */ => 'E', "\xE1\xBA\xBB" /* ẻ */ => 'e',
+ "\xE1\xBB\x82" /* Ể */ => 'E', "\xE1\xBB\x83" /* ể */ => 'e',
+ "\xE1\xBB\x88" /* Ỉ */ => 'I', "\xE1\xBB\x89" /* ỉ */ => 'i',
+ "\xE1\xBB\x8E" /* Ỏ */ => 'O', "\xE1\xBB\x8F" /* ỏ */ => 'o',
+ "\xE1\xBB\x94" /* Ổ */ => 'O', "\xE1\xBB\x95" /* ổ */ => 'o',
+ "\xE1\xBB\x9E" /* Ở */ => 'O', "\xE1\xBB\x9F" /* ở */ => 'o',
+ "\xE1\xBB\xA6" /* Ủ */ => 'U', "\xE1\xBB\xA7" /* ủ */ => 'u',
+ "\xE1\xBB\xAC" /* Ử */ => 'U', "\xE1\xBB\xAD" /* ử */ => 'u',
+ "\xE1\xBB\xB6" /* Ỷ */ => 'Y', "\xE1\xBB\xB7" /* ỷ */ => 'y',
+ // tilde
+ "\xE1\xBA\xAA" /* Ẫ */ => 'A', "\xE1\xBA\xAB" /* ẫ */ => 'a',
+ "\xE1\xBA\xB4" /* Ẵ */ => 'A', "\xE1\xBA\xB5" /* ẵ */ => 'a',
+ "\xE1\xBA\xBC" /* Ẽ */ => 'E', "\xE1\xBA\xBD" /* ẽ */ => 'e',
+ "\xE1\xBB\x84" /* Ễ */ => 'E', "\xE1\xBB\x85" /* ễ */ => 'e',
+ "\xE1\xBB\x96" /* Ỗ */ => 'O', "\xE1\xBB\x97" /* ỗ */ => 'o',
+ "\xE1\xBB\xA0" /* Ỡ */ => 'O', "\xE1\xBB\xA1" /* ỡ */ => 'o',
+ "\xE1\xBB\xAE" /* Ữ */ => 'U', "\xE1\xBB\xAF" /* ữ */ => 'u',
+ "\xE1\xBB\xB8" /* Ỹ */ => 'Y', "\xE1\xBB\xB9" /* ỹ */ => 'y',
+ // acute accent
+ "\xE1\xBA\xA4" /* Ấ */ => 'A', "\xE1\xBA\xA5" /* ấ */ => 'a',
+ "\xE1\xBA\xAE" /* Ắ */ => 'A', "\xE1\xBA\xAF" /* ắ */ => 'a',
+ "\xE1\xBA\xBE" /* Ế */ => 'E', "\xE1\xBA\xBF" /* ế */ => 'e',
+ "\xE1\xBB\x90" /* Ố */ => 'O', "\xE1\xBB\x91" /* ố */ => 'o',
+ "\xE1\xBB\x9A" /* Ớ */ => 'O', "\xE1\xBB\x9B" /* ớ */ => 'o',
+ "\xE1\xBB\xA8" /* Ứ */ => 'U', "\xE1\xBB\xA9" /* ứ */ => 'u',
+ // dot below
+ "\xE1\xBA\xA0" /* Ạ */ => 'A', "\xE1\xBA\xA1" /* ạ */ => 'a',
+ "\xE1\xBA\xAC" /* Ậ */ => 'A', "\xE1\xBA\xAD" /* ậ */ => 'a',
+ "\xE1\xBA\xB6" /* Ặ */ => 'A', "\xE1\xBA\xB7" /* ặ */ => 'a',
+ "\xE1\xBA\xB8" /* Ẹ */ => 'E', "\xE1\xBA\xB9" /* ẹ */ => 'e',
+ "\xE1\xBB\x86" /* Ệ */ => 'E', "\xE1\xBB\x87" /* ệ */ => 'e',
+ "\xE1\xBB\x8A" /* Ị */ => 'I', "\xE1\xBB\x8B" /* ị */ => 'i',
+ "\xE1\xBB\x8C" /* Ọ */ => 'O', "\xE1\xBB\x8D" /* ọ */ => 'o',
+ "\xE1\xBB\x98" /* Ộ */ => 'O', "\xE1\xBB\x99" /* ộ */ => 'o',
+ "\xE1\xBB\xA2" /* Ợ */ => 'O', "\xE1\xBB\xA3" /* ợ */ => 'o',
+ "\xE1\xBB\xA4" /* Ụ */ => 'U', "\xE1\xBB\xA5" /* ụ */ => 'u',
+ "\xE1\xBB\xB0" /* Ự */ => 'U', "\xE1\xBB\xB1" /* ự */ => 'u',
+ "\xE1\xBB\xB4" /* Ỵ */ => 'Y', "\xE1\xBB\xB5" /* ỵ */ => 'y',
+ );
+ }
+
+ /**
+ * Tests that "normalizer_normalize" exists and works
+ *
+ * @return bool
+ */
+ static public function hasNormalizerSupport() {
+ static $ret = null;
+ if (null === $ret) {
+ $form_c = "\xC3\x85"; // 'LATIN CAPITAL LETTER A WITH RING ABOVE' (U+00C5)
+ $form_d = "A\xCC\x8A"; // A followed by 'COMBINING RING ABOVE' (U+030A)
+ $ret = (function_exists('normalizer_normalize')
+ && $form_c === normalizer_normalize($form_d));
+ }
+ return $ret;
+ }
+}
diff --git a/engine/classes/ElggUser.php b/engine/classes/ElggUser.php
index 071806529..6163f9b62 100644
--- a/engine/classes/ElggUser.php
+++ b/engine/classes/ElggUser.php
@@ -6,6 +6,15 @@
*
* @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 {
@@ -31,6 +40,9 @@ class ElggUser extends ElggEntity
$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;
}
@@ -38,7 +50,7 @@ class ElggUser extends ElggEntity
* Construct a new user entity, optionally from a given id value.
*
* @param mixed $guid If an int, load that GUID.
- * If a db row then will attempt to load the rest of the data.
+ * If an entity table db row then will load the rest of the data.
*
* @throws Exception if there was a problem creating the user.
*/
@@ -49,35 +61,33 @@ class ElggUser extends ElggEntity
$this->initialise_attributes(false);
if (!empty($guid)) {
- // Is $guid is a DB row - either a entity row, or a user table row.
+ // Is $guid is a DB entity row
if ($guid instanceof stdClass) {
// Load the rest
- if (!$this->load($guid->guid)) {
+ if (!$this->load($guid)) {
$msg = elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid->guid));
throw new IOException($msg);
}
-
- // See if this is a username
} else if (is_string($guid)) {
- $guid = get_user_by_username($guid);
- foreach ($guid->attributes as $key => $value) {
- $this->attributes[$key] = $value;
+ // $guid is a username
+ $user = get_user_by_username($guid);
+ if ($user) {
+ foreach ($user->attributes as $key => $value) {
+ $this->attributes[$key] = $value;
+ }
}
-
- // Is $guid is an ElggUser? Use a copy constructor
} else if ($guid instanceof ElggUser) {
+ // $guid is an ElggUser so this is a copy constructor
elgg_deprecated_notice('This type of usage of the ElggUser constructor was deprecated. Please use the clone method.', 1.7);
foreach ($guid->attributes as $key => $value) {
$this->attributes[$key] = $value;
}
-
- // Is this is an ElggEntity but not an ElggUser = ERROR!
} else if ($guid instanceof ElggEntity) {
+ // @todo why have a special case here
throw new InvalidParameterException(elgg_echo('InvalidParameterException:NonElggUser'));
-
- // We assume if we have got this far, $guid is an int
} else if (is_numeric($guid)) {
+ // $guid is a GUID so load entity
if (!$this->load($guid)) {
throw new IOException(elgg_echo('IOException:FailedToLoadGUID', array(get_class(), $guid)));
}
@@ -88,38 +98,24 @@ class ElggUser extends ElggEntity
}
/**
- * Override the load function.
- * This function will ensure that all data is loaded (were possible), so
- * if only part of the ElggUser is loaded, it'll load the rest.
+ * Load the ElggUser data from the database
*
- * @param int $guid ElggUser GUID
+ * @param mixed $guid ElggUser GUID or stdClass database row from entity table
*
- * @return true|false
+ * @return bool
*/
protected function load($guid) {
- // Test to see if we have the generic stuff
- if (!parent::load($guid)) {
- return false;
- }
-
- // Check the type
- if ($this->attributes['type'] != 'user') {
- $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class()));
- throw new InvalidClassException($msg);
- }
+ $attr_loader = new ElggAttributeLoader(get_class(), 'user', $this->attributes);
+ $attr_loader->secondary_loader = 'get_user_entity_as_row';
- // Load missing data
- $row = get_user_entity_as_row($guid);
- if (($row) && (!$this->isFullyLoaded())) {
- // If $row isn't a cached copy then increment the counter
- $this->attributes['tables_loaded'] ++;
+ $attrs = $attr_loader->getRequiredAttributes($guid);
+ if (!$attrs) {
+ return false;
}
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach ($objarray as $key => $value) {
- $this->attributes[$key] = $value;
- }
+ $this->attributes = $attrs;
+ $this->attributes['tables_loaded'] = 2;
+ _elgg_cache_entity($this);
return true;
}
@@ -127,7 +123,7 @@ class ElggUser extends ElggEntity
/**
* Saves this user to the database.
*
- * @return true|false
+ * @return bool
*/
public function save() {
// Save generic stuff
@@ -136,9 +132,13 @@ class ElggUser extends ElggEntity
}
// Now save specific stuff
- return create_user_entity($this->get('guid'), $this->get('name'), $this->get('username'),
+ _elgg_disable_caching_for_entity($this->guid);
+ $ret = create_user_entity($this->get('guid'), $this->get('name'), $this->get('username'),
$this->get('password'), $this->get('salt'), $this->get('email'), $this->get('language'),
$this->get('code'));
+ _elgg_enable_caching_for_entity($this->guid);
+
+ return $ret;
}
/**
@@ -157,9 +157,6 @@ class ElggUser extends ElggEntity
unset($CODE_TO_GUID_MAP_CACHE[$this->code]);
}
- // Delete owned data
- clear_annotations_by_owner($this->guid);
- clear_metadata_by_owner($this->guid);
clear_user_files($this);
// Delete entity
@@ -250,7 +247,7 @@ class ElggUser extends ElggEntity
* @param int $limit The number of results to return
* @param int $offset Any indexing offset
*
- * @return bool
+ * @return array
*/
function getSites($subtype = "", $limit = 10, $offset = 0) {
return get_user_sites($this->getGUID(), $subtype, $limit, $offset);
@@ -261,7 +258,7 @@ class ElggUser extends ElggEntity
*
* @param int $site_guid The guid of the site to add it to
*
- * @return true|false
+ * @return bool
*/
function addToSite($site_guid) {
return add_site_user($site_guid, $this->getGUID());
@@ -272,29 +269,29 @@ class ElggUser extends ElggEntity
*
* @param int $site_guid The guid of the site to remove it from
*
- * @return true|false
+ * @return bool
*/
function removeFromSite($site_guid) {
return remove_site_user($site_guid, $this->getGUID());
}
/**
- * Adds a user to this user's friends list
+ * Adds a user as a friend
*
* @param int $friend_guid The GUID of the user to add
*
- * @return true|false Depending on success
+ * @return bool
*/
function addFriend($friend_guid) {
return user_add_friend($this->getGUID(), $friend_guid);
}
/**
- * Removes a user from this user's friends list
+ * Removes a user as a friend
*
* @param int $friend_guid The GUID of the user to remove
*
- * @return true|false Depending on success
+ * @return bool
*/
function removeFriend($friend_guid) {
return user_remove_friend($this->getGUID(), $friend_guid);
@@ -303,62 +300,141 @@ class ElggUser extends ElggEntity
/**
* Determines whether or not this user is a friend of the currently logged in user
*
- * @return true|false
+ * @return bool
*/
function isFriend() {
- return user_is_friend(get_loggedin_userid(), $this->getGUID());
+ return $this->isFriendOf(elgg_get_logged_in_user_guid());
}
/**
* Determines whether this user is friends with another user
*
- * @param int $user_guid The GUID of the user to check is on this user's friends list
+ * @param int $user_guid The GUID of the user to check against
*
- * @return true|false
+ * @return bool
*/
function isFriendsWith($user_guid) {
return user_is_friend($this->getGUID(), $user_guid);
}
/**
- * Determines whether or not this user is on another user's friends list
+ * Determines whether or not this user is another user's friend
*
* @param int $user_guid The GUID of the user to check against
*
- * @return true|false
+ * @return bool
*/
function isFriendOf($user_guid) {
return user_is_friend($user_guid, $this->getGUID());
}
/**
- * Retrieves a list of this user's friends
+ * Gets this user's friends
*
- * @param string $subtype Optionally, the subtype of user to filter to (leave blank for all)
+ * @param string $subtype Optionally, the user subtype (leave blank for all)
* @param int $limit The number of users to retrieve
* @param int $offset Indexing offset, if any
*
- * @return array|false Array of ElggUsers, or false, depending on success
+ * @return array|false Array of ElggUser, or false, depending on success
*/
function getFriends($subtype = "", $limit = 10, $offset = 0) {
return get_user_friends($this->getGUID(), $subtype, $limit, $offset);
}
/**
- * Retrieves a list of people who have made this user a friend
+ * Gets users who have made this user a friend
*
- * @param string $subtype Optionally, the subtype of user to filter to (leave blank for all)
+ * @param string $subtype Optionally, the user subtype (leave blank for all)
* @param int $limit The number of users to retrieve
* @param int $offset Indexing offset, if any
*
- * @return array|false Array of ElggUsers, or false, depending on success
+ * @return array|false Array of ElggUser, or false, depending on success
*/
function getFriendsOf($subtype = "", $limit = 10, $offset = 0) {
return get_user_friends_of($this->getGUID(), $subtype, $limit, $offset);
}
/**
- * Get an array of ElggObjects owned by this user.
+ * Lists the user's friends
+ *
+ * @param string $subtype Optionally, the user subtype (leave blank for all)
+ * @param int $limit The number of users to retrieve
+ * @param array $vars Display variables for the user view
+ *
+ * @return string Rendered list of friends
+ * @since 1.8.0
+ */
+ function listFriends($subtype = "", $limit = 10, array $vars = array()) {
+ $defaults = array(
+ 'type' => 'user',
+ 'relationship' => 'friend',
+ 'relationship_guid' => $this->guid,
+ 'limit' => $limit,
+ 'full_view' => false,
+ );
+
+ $options = array_merge($defaults, $vars);
+
+ if ($subtype) {
+ $options['subtype'] = $subtype;
+ }
+
+ return elgg_list_entities_from_relationship($options);
+ }
+
+ /**
+ * Gets the user's groups
+ *
+ * @param string $subtype Optionally, the subtype of user to filter to (leave blank for all)
+ * @param int $limit The number of groups to retrieve
+ * @param int $offset Indexing offset, if any
+ *
+ * @return array|false Array of ElggGroup, or false, depending on success
+ */
+ function getGroups($subtype = "", $limit = 10, $offset = 0) {
+ $options = array(
+ 'type' => 'group',
+ 'relationship' => 'member',
+ 'relationship_guid' => $this->guid,
+ 'limit' => $limit,
+ 'offset' => $offset,
+ );
+
+ if ($subtype) {
+ $options['subtype'] = $subtype;
+ }
+
+ return elgg_get_entities_from_relationship($options);
+ }
+
+ /**
+ * Lists the user's groups
+ *
+ * @param string $subtype Optionally, the user subtype (leave blank for all)
+ * @param int $limit The number of users to retrieve
+ * @param int $offset Indexing offset, if any
+ *
+ * @return string
+ */
+ function listGroups($subtype = "", $limit = 10, $offset = 0) {
+ $options = array(
+ 'type' => 'group',
+ 'relationship' => 'member',
+ 'relationship_guid' => $this->guid,
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'full_view' => false,
+ );
+
+ if ($subtype) {
+ $options['subtype'] = $subtype;
+ }
+
+ return elgg_list_entities_from_relationship($options);
+ }
+
+ /**
+ * Get an array of ElggObject owned by this user.
*
* @param string $subtype The subtype of the objects, if any
* @param int $limit Number of results to return
@@ -367,7 +443,14 @@ class ElggUser extends ElggEntity
* @return array|false
*/
public function getObjects($subtype = "", $limit = 10, $offset = 0) {
- return get_user_objects($this->getGUID(), $subtype, $limit, $offset);
+ $params = array(
+ 'type' => 'object',
+ 'subtype' => $subtype,
+ 'owner_guid' => $this->getGUID(),
+ 'limit' => $limit,
+ 'offset' => $offset
+ );
+ return elgg_get_entities($params);
}
/**
@@ -404,7 +487,8 @@ class ElggUser extends ElggEntity
* @return array|false
*/
public function getCollections($subtype = "", $limit = 10, $offset = 0) {
- return get_user_collections($this->getGUID(), $subtype, $limit, $offset);
+ elgg_deprecated_notice("ElggUser::getCollections() has been deprecated", 1.8);
+ return false;
}
/**
@@ -458,7 +542,7 @@ class ElggUser extends ElggEntity
*/
public function __set($name, $value) {
if ($name == 'admin' || $name == 'siteadmin') {
- elgg_deprecated_notice('The admin/siteadmin metadata are not longer used. Use ElggUser->makeAdmin() and ElggUser->removeAdmin().', '1.7.1');
+ elgg_deprecated_notice('The admin/siteadmin metadata are not longer used. Use ElggUser->makeAdmin() and ElggUser->removeAdmin().', 1.7);
if ($value == 'yes' || $value == '1') {
$this->makeAdmin();
@@ -478,10 +562,27 @@ class ElggUser extends ElggEntity
*/
public function __get($name) {
if ($name == 'admin' || $name == 'siteadmin') {
- elgg_deprecated_notice('The admin/siteadmin metadata are not longer used. Use ElggUser->isAdmin().', '1.7.1');
+ elgg_deprecated_notice('The admin/siteadmin metadata are not longer used. Use ElggUser->isAdmin().', 1.7);
return $this->isAdmin();
}
return parent::__get($name);
}
+
+ /**
+ * Can a user comment on this user?
+ *
+ * @see ElggEntity::canComment()
+ *
+ * @param int $user_guid User guid (default is logged in user)
+ * @return bool
+ * @since 1.8.0
+ */
+ public function canComment($user_guid = 0) {
+ $result = parent::canComment($user_guid);
+ if ($result !== null) {
+ return $result;
+ }
+ return false;
+ }
}
diff --git a/engine/classes/ElggVolatileMetadataCache.php b/engine/classes/ElggVolatileMetadataCache.php
new file mode 100644
index 000000000..4acda7cee
--- /dev/null
+++ b/engine/classes/ElggVolatileMetadataCache.php
@@ -0,0 +1,355 @@
+<?php
+/**
+ * ElggVolatileMetadataCache
+ * In memory cache of known metadata values stored by entity.
+ *
+ * @package Elgg.Core
+ * @subpackage Cache
+ *
+ * @access private
+ */
+class ElggVolatileMetadataCache {
+
+ /**
+ * The cached values (or null for known to be empty). If the portion of the cache
+ * is synchronized, missing values are assumed to indicate that values do not
+ * exist in storage, otherwise, we don't know what's there.
+ *
+ * @var array
+ */
+ protected $values = array();
+
+ /**
+ * Does the cache know that it contains all names fetch-able from storage?
+ * The keys are entity GUIDs and either the value exists (true) or it's not set.
+ *
+ * @var array
+ */
+ protected $isSynchronized = array();
+
+ /**
+ * @var null|bool
+ */
+ protected $ignoreAccess = null;
+
+ /**
+ * Cache metadata for an entity
+ *
+ * @param int $entity_guid The GUID of the entity
+ * @param array $values The metadata values to cache
+ * @return void
+ */
+ public function saveAll($entity_guid, array $values) {
+ if (!$this->getIgnoreAccess()) {
+ $this->values[$entity_guid] = $values;
+ $this->isSynchronized[$entity_guid] = true;
+ }
+ }
+
+ /**
+ * Get the metadata for an entity
+ *
+ * @param int $entity_guid The GUID of the entity
+ * @return array
+ */
+ public function loadAll($entity_guid) {
+ if (isset($this->values[$entity_guid])) {
+ return $this->values[$entity_guid];
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Declare that there may be fetch-able metadata names in storage that this
+ * cache doesn't know about
+ *
+ * @param int $entity_guid The GUID of the entity
+ * @return void
+ */
+ public function markOutOfSync($entity_guid) {
+ unset($this->isSynchronized[$entity_guid]);
+ }
+
+ /**
+ * Have all the metadata for this entity been cached?
+ *
+ * @param int $entity_guid The GUID of the entity
+ * @return bool
+ */
+ public function isSynchronized($entity_guid) {
+ return isset($this->isSynchronized[$entity_guid]);
+ }
+
+ /**
+ * Cache a piece of metadata
+ *
+ * @param int $entity_guid The GUID of the entity
+ * @param string $name The metadata name
+ * @param array|int|string|null $value The metadata value. null means it is
+ * known that there is no fetch-able
+ * metadata under this name
+ * @param bool $allow_multiple Can the metadata be an array
+ * @return void
+ */
+ public function save($entity_guid, $name, $value, $allow_multiple = false) {
+ if ($this->getIgnoreAccess()) {
+ // we don't know if what gets saves here will be available to user once
+ // access control returns, hence it's best to forget :/
+ $this->markUnknown($entity_guid, $name);
+ } else {
+ if ($allow_multiple) {
+ if ($this->isKnown($entity_guid, $name)) {
+ $existing = $this->load($entity_guid, $name);
+ if ($existing !== null) {
+ $existing = (array) $existing;
+ $existing[] = $value;
+ $value = $existing;
+ }
+ } else {
+ // we don't know whether there are unknown values, so it's
+ // safest to leave that assumption
+ $this->markUnknown($entity_guid, $name);
+ return;
+ }
+ }
+ $this->values[$entity_guid][$name] = $value;
+ }
+ }
+
+ /**
+ * Warning: You should always call isKnown() beforehand to verify that this
+ * function's return value should be trusted (otherwise a null return value
+ * is ambiguous).
+ *
+ * @param int $entity_guid The GUID of the entity
+ * @param string $name The metadata name
+ * @return array|string|int|null null = value does not exist
+ */
+ public function load($entity_guid, $name) {
+ if (isset($this->values[$entity_guid]) && array_key_exists($name, $this->values[$entity_guid])) {
+ return $this->values[$entity_guid][$name];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Forget about this metadata entry. We don't want to try to guess what the
+ * next fetch from storage will return
+ *
+ * @param int $entity_guid The GUID of the entity
+ * @param string $name The metadata name
+ * @return void
+ */
+ public function markUnknown($entity_guid, $name) {
+ unset($this->values[$entity_guid][$name]);
+ $this->markOutOfSync($entity_guid);
+ }
+
+ /**
+ * If true, load() will return an accurate value for this name
+ *
+ * @param int $entity_guid The GUID of the entity
+ * @param string $name The metadata name
+ * @return bool
+ */
+ public function isKnown($entity_guid, $name) {
+ if (isset($this->isSynchronized[$entity_guid])) {
+ return true;
+ } else {
+ return (isset($this->values[$entity_guid]) && array_key_exists($name, $this->values[$entity_guid]));
+ }
+
+ }
+
+ /**
+ * Declare that metadata under this name is known to be not fetch-able from storage
+ *
+ * @param int $entity_guid The GUID of the entity
+ * @param string $name The metadata name
+ * @return array
+ */
+ public function markEmpty($entity_guid, $name) {
+ $this->values[$entity_guid][$name] = null;
+ }
+
+ /**
+ * Forget about all metadata for an entity
+ *
+ * @param int $entity_guid The GUID of the entity
+ * @return void
+ */
+ public function clear($entity_guid) {
+ $this->values[$entity_guid] = array();
+ $this->markOutOfSync($entity_guid);
+ }
+
+ /**
+ * Clear entire cache and mark all entities as out of sync
+ *
+ * @return void
+ */
+ public function flush() {
+ $this->values = array();
+ $this->isSynchronized = array();
+ }
+
+ /**
+ * Use this value instead of calling elgg_get_ignore_access(). By default that
+ * function will be called.
+ *
+ * This setting makes this component a little more loosely-coupled.
+ *
+ * @param bool $ignore Whether to ignore access or not
+ * @return void
+ */
+ public function setIgnoreAccess($ignore) {
+ $this->ignoreAccess = (bool) $ignore;
+ }
+
+ /**
+ * Tell the cache to call elgg_get_ignore_access() to determing access status.
+ *
+ * @return void
+ */
+ public function unsetIgnoreAccess() {
+ $this->ignoreAccess = null;
+ }
+
+ /**
+ * Get the ignore access value
+ *
+ * @return bool
+ */
+ protected function getIgnoreAccess() {
+ if (null === $this->ignoreAccess) {
+ return elgg_get_ignore_access();
+ } else {
+ return $this->ignoreAccess;
+ }
+ }
+
+ /**
+ * Invalidate based on options passed to the global *_metadata functions
+ *
+ * @param string $action Action performed on metadata. "delete", "disable", or "enable"
+ * @param array $options Options passed to elgg_(delete|disable|enable)_metadata
+ * "guid" if given, invalidation will be limited to this entity
+ * "metadata_name" if given, invalidation will be limited to metadata with this name
+ * @return void
+ */
+ public function invalidateByOptions($action, array $options) {
+ // remove as little as possible, optimizing for common cases
+ if (empty($options['guid'])) {
+ // safest to clear everything unless we want to make this even more complex :(
+ $this->flush();
+ } else {
+ if (empty($options['metadata_name'])) {
+ // safest to clear the whole entity
+ $this->clear($options['guid']);
+ } else {
+ switch ($action) {
+ case 'delete':
+ $this->markEmpty($options['guid'], $options['metadata_name']);
+ break;
+ default:
+ $this->markUnknown($options['guid'], $options['metadata_name']);
+ }
+ }
+ }
+ }
+
+ /**
+ * Populate the cache from a set of entities
+ *
+ * @param int|array $guids Array of or single GUIDs
+ * @return void
+ */
+ public function populateFromEntities($guids) {
+ if (empty($guids)) {
+ return;
+ }
+ if (!is_array($guids)) {
+ $guids = array($guids);
+ }
+ $guids = array_unique($guids);
+
+ // could be useful at some point in future
+ //$guids = $this->filterMetadataHeavyEntities($guids);
+
+ $db_prefix = elgg_get_config('dbprefix');
+ $options = array(
+ 'guids' => $guids,
+ 'limit' => 0,
+ 'callback' => false,
+ 'joins' => array(
+ "JOIN {$db_prefix}metastrings v ON n_table.value_id = v.id",
+ "JOIN {$db_prefix}metastrings n ON n_table.name_id = n.id",
+ ),
+ 'selects' => array('n.string AS name', 'v.string AS value'),
+ 'order_by' => 'n_table.entity_guid, n_table.time_created ASC',
+
+ // @todo don't know why this is necessary
+ 'wheres' => array(get_access_sql_suffix('n_table')),
+ );
+ $data = elgg_get_metadata($options);
+
+ // build up metadata for each entity, save when GUID changes (or data ends)
+ $last_guid = null;
+ $metadata = array();
+ $last_row_idx = count($data) - 1;
+ foreach ($data as $i => $row) {
+ $name = $row->name;
+ $value = ($row->value_type === 'text') ? $row->value : (int) $row->value;
+ $guid = $row->entity_guid;
+ if ($guid !== $last_guid) {
+ if ($last_guid) {
+ $this->saveAll($last_guid, $metadata);
+ }
+ $metadata = array();
+ }
+ if (isset($metadata[$name])) {
+ $metadata[$name] = (array) $metadata[$name];
+ $metadata[$name][] = $value;
+ } else {
+ $metadata[$name] = $value;
+ }
+ if (($i == $last_row_idx)) {
+ $this->saveAll($guid, $metadata);
+ }
+ $last_guid = $guid;
+ }
+ }
+
+ /**
+ * Filter out entities whose concatenated metadata values (INTs casted as string)
+ * exceed a threshold in characters. This could be used to avoid overpopulating the
+ * cache if RAM usage becomes an issue.
+ *
+ * @param array $guids GUIDs of entities to examine
+ * @param int $limit Limit in characters of all metadata (with ints casted to strings)
+ * @return array
+ */
+ public function filterMetadataHeavyEntities(array $guids, $limit = 1024000) {
+ $db_prefix = elgg_get_config('dbprefix');
+
+ $options = array(
+ 'guids' => $guids,
+ 'limit' => 0,
+ 'callback' => false,
+ 'joins' => "JOIN {$db_prefix}metastrings v ON n_table.value_id = v.id",
+ 'selects' => array('SUM(LENGTH(v.string)) AS bytes'),
+ 'order_by' => 'n_table.entity_guid, n_table.time_created ASC',
+ 'group_by' => 'n_table.entity_guid',
+ );
+ $data = elgg_get_metadata($options);
+ // don't cache if metadata for entity is over 10MB (or rolled INT)
+ foreach ($data as $row) {
+ if ($row->bytes > $limit || $row->bytes < 0) {
+ array_splice($guids, array_search($row->entity_guid, $guids), 1);
+ }
+ }
+ return $guids;
+ }
+}
diff --git a/engine/classes/ElggWidget.php b/engine/classes/ElggWidget.php
index 60f2b6be4..66191bf47 100644
--- a/engine/classes/ElggWidget.php
+++ b/engine/classes/ElggWidget.php
@@ -7,6 +7,11 @@
*
* @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 {
@@ -30,12 +35,12 @@ class ElggWidget extends ElggObject {
*/
public function get($name) {
// See if its in our base attribute
- if (isset($this->attributes[$name])) {
+ if (array_key_exists($name, $this->attributes)) {
return $this->attributes[$name];
}
// No, so see if its in the private data store.
- $meta = get_private_setting($this->guid, $name);
+ $meta = $this->getPrivateSetting($name);
if ($meta) {
return $meta;
}
@@ -61,7 +66,7 @@ class ElggWidget extends ElggObject {
$this->attributes[$name] = $value;
} else {
- return set_private_setting($this->guid, $name, $value);
+ return $this->setPrivateSetting($name, $value);
}
return true;
@@ -75,7 +80,7 @@ class ElggWidget extends ElggObject {
* @since 1.8.0
*/
public function setContext($context) {
- return set_private_setting($this->guid, 'context', $context);
+ return $this->setPrivateSetting('context', $context);
}
/**
@@ -85,7 +90,7 @@ class ElggWidget extends ElggObject {
* @since 1.8.0
*/
public function getContext() {
- return get_private_setting($this->guid, 'context');
+ return $this->getPrivateSetting('context');
}
/**
@@ -115,6 +120,8 @@ class ElggWidget extends ElggObject {
$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)
@@ -129,21 +136,65 @@ class ElggWidget extends ElggObject {
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 = $widgets[0]->order - 10;
- } elseif ($rank == count($widgets)) {
- // bottom 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 that are below
- $this->order = $widgets[$rank]->order;
- for ($index = $rank; $index < count($widgets); $index++) {
- if ($widgets[$index]->guid != $this->guid) {
- $widgets[$index]-> order += 10;
+ // 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;
}
@@ -191,4 +242,4 @@ class ElggWidget extends ElggObject {
return true;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ElggXMLElement.php b/engine/classes/ElggXMLElement.php
new file mode 100644
index 000000000..cbd3fc5ce
--- /dev/null
+++ b/engine/classes/ElggXMLElement.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * A parser for XML that uses SimpleXMLElement
+ *
+ * @package Elgg.Core
+ * @subpackage XML
+ */
+class ElggXMLElement {
+ /**
+ * @var SimpleXMLElement
+ */
+ private $_element;
+
+ /**
+ * Creates an ElggXMLParser from a string or existing SimpleXMLElement
+ *
+ * @param string|SimpleXMLElement $xml The XML to parse
+ */
+ public function __construct($xml) {
+ if ($xml instanceof SimpleXMLElement) {
+ $this->_element = $xml;
+ } else {
+ // do not load entities
+ $disable_load_entities = libxml_disable_entity_loader(true);
+
+ $this->_element = new SimpleXMLElement($xml);
+
+ libxml_disable_entity_loader($disable_load_entities);
+ }
+ }
+
+ /**
+ * @return string The name of the element
+ */
+ public function getName() {
+ return $this->_element->getName();
+ }
+
+ /**
+ * @return string[] The attributes
+ */
+ public function getAttributes() {
+ //include namespace declarations as attributes
+ $xmlnsRaw = $this->_element->getNamespaces();
+ $xmlns = array();
+ foreach ($xmlnsRaw as $key => $val) {
+ $label = 'xmlns' . ($key ? ":$key" : $key);
+ $xmlns[$label] = $val;
+ }
+ //get attributes and merge with namespaces
+ $attrRaw = $this->_element->attributes();
+ $attr = array();
+ foreach ($attrRaw as $key => $val) {
+ $attr[$key] = $val;
+ }
+ $attr = array_merge((array) $xmlns, (array) $attr);
+ $result = array();
+ foreach ($attr as $key => $val) {
+ $result[$key] = (string) $val;
+ }
+ return $result;
+ }
+
+ /**
+ * @return string CData
+ */
+ public function getContent() {
+ return (string) $this->_element;
+ }
+
+ /**
+ * @return ElggXMLElement[] Child elements
+ */
+ public function getChildren() {
+ $children = $this->_element->children();
+ $result = array();
+ foreach ($children as $val) {
+ $result[] = new ElggXMLElement($val);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Override ->
+ *
+ * @param string $name Property name
+ * @return mixed
+ */
+ function __get($name) {
+ switch ($name) {
+ case 'name':
+ return $this->getName();
+ break;
+ case 'attributes':
+ return $this->getAttributes();
+ break;
+ case 'content':
+ return $this->getContent();
+ break;
+ case 'children':
+ return $this->getChildren();
+ break;
+ }
+ return null;
+ }
+
+ /**
+ * Override isset
+ *
+ * @param string $name Property name
+ * @return boolean
+ */
+ function __isset($name) {
+ switch ($name) {
+ case 'name':
+ return $this->getName() !== null;
+ break;
+ case 'attributes':
+ return $this->getAttributes() !== null;
+ break;
+ case 'content':
+ return $this->getContent() !== null;
+ break;
+ case 'children':
+ return $this->getChildren() !== null;
+ break;
+ }
+ return false;
+ }
+}
diff --git a/engine/classes/ErrorResult.php b/engine/classes/ErrorResult.php
index 8dbb7c13f..afad4c740 100644
--- a/engine/classes/ErrorResult.php
+++ b/engine/classes/ErrorResult.php
@@ -26,7 +26,7 @@ class ErrorResult extends GenericResult {
*
* @return void
*/
- public function ErrorResult($message, $code = "", Exception $exception = NULL) {
+ public function __construct($message, $code = "", Exception $exception = NULL) {
if ($code == "") {
$code = ErrorResult::$RESULT_FAIL;
}
@@ -51,4 +51,4 @@ class ErrorResult extends GenericResult {
// Return a new error object.
return new ErrorResult($message, $code, $exception);
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ExportException.php b/engine/classes/ExportException.php
index 11781d91b..ae8a8e41b 100644
--- a/engine/classes/ExportException.php
+++ b/engine/classes/ExportException.php
@@ -6,4 +6,4 @@
* @subpackage Exception
*
*/
-class ExportException extends DataFormatException {} \ No newline at end of file
+class ExportException extends DataFormatException {}
diff --git a/engine/classes/Exportable.php b/engine/classes/Exportable.php
index 1adcd781e..0c1ea5282 100644
--- a/engine/classes/Exportable.php
+++ b/engine/classes/Exportable.php
@@ -20,4 +20,4 @@ interface Exportable {
* @return array
*/
public function getExportableValues();
-} \ No newline at end of file
+}
diff --git a/engine/classes/Friendable.php b/engine/classes/Friendable.php
index 588f9ec5a..c308b4598 100644
--- a/engine/classes/Friendable.php
+++ b/engine/classes/Friendable.php
@@ -101,4 +101,4 @@ interface Friendable {
* @return int
*/
public function countObjects($subtype = "");
-} \ No newline at end of file
+}
diff --git a/engine/classes/GenericResult.php b/engine/classes/GenericResult.php
index bb8a7e76e..e42e924d1 100644
--- a/engine/classes/GenericResult.php
+++ b/engine/classes/GenericResult.php
@@ -122,4 +122,4 @@ abstract class GenericResult {
return $result;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/ImportException.php b/engine/classes/ImportException.php
index 14287fdf2..909c599d5 100644
--- a/engine/classes/ImportException.php
+++ b/engine/classes/ImportException.php
@@ -5,4 +5,4 @@
* @package Elgg.Core
* @subpackage Exception
*/
-class ImportException extends DataFormatException {} \ No newline at end of file
+class ImportException extends DataFormatException {}
diff --git a/engine/classes/IncompleteEntityException.php b/engine/classes/IncompleteEntityException.php
new file mode 100644
index 000000000..8c86edcc6
--- /dev/null
+++ b/engine/classes/IncompleteEntityException.php
@@ -0,0 +1,10 @@
+<?php
+/**
+ * IncompleteEntityException
+ * Thrown when constructing an entity that is missing its secondary entity table
+ *
+ * @package Elgg.Core
+ * @subpackage Exception
+ * @access private
+ */
+class IncompleteEntityException extends Exception {}
diff --git a/engine/classes/Locatable.php b/engine/classes/Locatable.php
index 980256cbb..7287d9798 100644
--- a/engine/classes/Locatable.php
+++ b/engine/classes/Locatable.php
@@ -13,7 +13,7 @@ interface Locatable {
* @param string $location Textual representation of location
*
* @return bool
- **/
+ */
public function setLocation($location);
/**
@@ -46,4 +46,4 @@ interface Locatable {
* @return string
*/
public function getLocation();
-} \ No newline at end of file
+}
diff --git a/engine/classes/Loggable.php b/engine/classes/Loggable.php
index 40690f87a..b9e8bf26b 100644
--- a/engine/classes/Loggable.php
+++ b/engine/classes/Loggable.php
@@ -62,4 +62,4 @@ interface Loggable {
* @deprecated 1.8 Use getOwnerGUID() instead
*/
public function getObjectOwnerGUID();
-} \ No newline at end of file
+}
diff --git a/engine/classes/LoginException.php b/engine/classes/LoginException.php
index 9189a6afd..7546fa36f 100644
--- a/engine/classes/LoginException.php
+++ b/engine/classes/LoginException.php
@@ -7,4 +7,4 @@
* @package Elgg.Core
* @subpackage Exceptions.Stub
*/
-class LoginException extends Exception {} \ No newline at end of file
+class LoginException extends Exception {}
diff --git a/engine/classes/Notable.php b/engine/classes/Notable.php
index e36699dfc..0c21af27d 100644
--- a/engine/classes/Notable.php
+++ b/engine/classes/Notable.php
@@ -38,4 +38,4 @@ interface Notable {
* @return int
*/
public function getCalendarEndTime();
-} \ No newline at end of file
+}
diff --git a/engine/classes/ODDDocument.php b/engine/classes/ODDDocument.php
index 4d185aba5..540c35a3b 100644
--- a/engine/classes/ODDDocument.php
+++ b/engine/classes/ODDDocument.php
@@ -70,8 +70,8 @@ class ODDDocument implements Iterator {
public function addElement(ODD $element) {
if (!is_array($this->elements)) {
$this->elements = array();
- $this->elements[] = $element;
}
+ $this->elements[] = $element;
}
/**
diff --git a/engine/classes/ODDEntity.php b/engine/classes/ODDEntity.php
index acdfe2f71..e9bb5da6a 100644
--- a/engine/classes/ODDEntity.php
+++ b/engine/classes/ODDEntity.php
@@ -32,75 +32,3 @@ class ODDEntity extends ODD {
return "entity";
}
}
-
-/**
- * ODD Metadata class.
- *
- * @package Elgg.Core
- * @subpackage ODD
- */
-class ODDMetaData extends ODD {
-
- /**
- * New ODD metadata
- *
- * @param unknown_type $uuid Unique ID
- * @param unknown_type $entity_uuid Another unique ID
- * @param unknown_type $name Name
- * @param unknown_type $value Value
- * @param unknown_type $type Type
- * @param unknown_type $owner_uuid Owner ID
- */
- function __construct($uuid, $entity_uuid, $name, $value, $type = "", $owner_uuid = "") {
- parent::__construct();
-
- $this->setAttribute('uuid', $uuid);
- $this->setAttribute('entity_uuid', $entity_uuid);
- $this->setAttribute('name', $name);
- $this->setAttribute('type', $type);
- $this->setAttribute('owner_uuid', $owner_uuid);
- $this->setBody($value);
- }
-
- /**
- * Returns 'metadata'
- *
- * @return 'metadata'
- */
- protected function getTagName() {
- return "metadata";
- }
-}
-
-/**
- * ODD Relationship class.
- *
- * @package Elgg
- * @subpackage Core
- */
-class ODDRelationship extends ODD {
-
- /**
- * New ODD Relationship
- *
- * @param unknown_type $uuid1 First UUID
- * @param unknown_type $type Type of telationship
- * @param unknown_type $uuid2 Second UUId
- */
- function __construct($uuid1, $type, $uuid2) {
- parent::__construct();
-
- $this->setAttribute('uuid1', $uuid1);
- $this->setAttribute('type', $type);
- $this->setAttribute('uuid2', $uuid2);
- }
-
- /**
- * Returns 'relationship'
- *
- * @return 'relationship'
- */
- protected function getTagName() {
- return "relationship";
- }
-} \ No newline at end of file
diff --git a/engine/classes/ODDMetaData.php b/engine/classes/ODDMetaData.php
new file mode 100644
index 000000000..09b653582
--- /dev/null
+++ b/engine/classes/ODDMetaData.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * ODD Metadata class.
+ *
+ * @package Elgg.Core
+ * @subpackage ODD
+ */
+class ODDMetaData extends ODD {
+
+ /**
+ * New ODD metadata
+ *
+ * @param string $uuid Unique ID
+ * @param string $entity_uuid Another unique ID
+ * @param string $name Name
+ * @param string $value Value
+ * @param string $type Type
+ * @param string $owner_uuid Owner ID
+ */
+ function __construct($uuid, $entity_uuid, $name, $value, $type = "", $owner_uuid = "") {
+ parent::__construct();
+
+ $this->setAttribute('uuid', $uuid);
+ $this->setAttribute('entity_uuid', $entity_uuid);
+ $this->setAttribute('name', $name);
+ $this->setAttribute('type', $type);
+ $this->setAttribute('owner_uuid', $owner_uuid);
+ $this->setBody($value);
+ }
+
+ /**
+ * Returns 'metadata'
+ *
+ * @return string 'metadata'
+ */
+ protected function getTagName() {
+ return "metadata";
+ }
+}
diff --git a/engine/classes/ODDRelationship.php b/engine/classes/ODDRelationship.php
new file mode 100644
index 000000000..8b1fe217b
--- /dev/null
+++ b/engine/classes/ODDRelationship.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * ODD Relationship class.
+ *
+ * @package Elgg
+ * @subpackage Core
+ */
+class ODDRelationship extends ODD {
+
+ /**
+ * New ODD Relationship
+ *
+ * @param string $uuid1 First UUID
+ * @param string $type Type of telationship
+ * @param string $uuid2 Second UUId
+ */
+ function __construct($uuid1, $type, $uuid2) {
+ parent::__construct();
+
+ $this->setAttribute('uuid1', $uuid1);
+ $this->setAttribute('type', $type);
+ $this->setAttribute('uuid2', $uuid2);
+ }
+
+ /**
+ * Returns 'relationship'
+ *
+ * @return string 'relationship'
+ */
+ protected function getTagName() {
+ return "relationship";
+ }
+}
diff --git a/engine/classes/PluginException.php b/engine/classes/PluginException.php
index 56cf29596..a74303695 100644
--- a/engine/classes/PluginException.php
+++ b/engine/classes/PluginException.php
@@ -8,4 +8,4 @@
* @package Elgg.Core
* @subpackage Exception
*/
-class PluginException extends Exception {} \ No newline at end of file
+class PluginException extends Exception {}
diff --git a/engine/classes/RegistrationException.php b/engine/classes/RegistrationException.php
index 05af35a01..5246efc25 100644
--- a/engine/classes/RegistrationException.php
+++ b/engine/classes/RegistrationException.php
@@ -6,4 +6,4 @@
* @package Elgg.Core
* @subpackage Exceptions
*/
-class RegistrationException extends InstallationException {} \ No newline at end of file
+class RegistrationException extends InstallationException {}
diff --git a/engine/classes/SuccessResult.php b/engine/classes/SuccessResult.php
index f68ddd39e..ab5468ad8 100644
--- a/engine/classes/SuccessResult.php
+++ b/engine/classes/SuccessResult.php
@@ -15,7 +15,7 @@ class SuccessResult extends GenericResult {
*
* @param string $result The result
*/
- public function SuccessResult($result) {
+ public function __construct($result) {
$this->setResult($result);
$this->setStatusCode(SuccessResult::$RESULT_SUCCESS);
}
@@ -31,4 +31,4 @@ class SuccessResult extends GenericResult {
// Return a new error object.
return new SuccessResult($result);
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/XMLRPCArrayParameter.php b/engine/classes/XMLRPCArrayParameter.php
index c0d358d5a..a8edccba7 100644
--- a/engine/classes/XMLRPCArrayParameter.php
+++ b/engine/classes/XMLRPCArrayParameter.php
@@ -53,4 +53,4 @@ class XMLRPCArrayParameter extends XMLRPCParameter
return "<array><data>$params</data></array>";
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/XMLRPCBase64Parameter.php b/engine/classes/XMLRPCBase64Parameter.php
index eb82b24a8..7db0a761c 100644
--- a/engine/classes/XMLRPCBase64Parameter.php
+++ b/engine/classes/XMLRPCBase64Parameter.php
@@ -25,4 +25,4 @@ class XMLRPCBase64Parameter extends XMLRPCParameter {
function __toString() {
return "<value><base64>{$value}</base64></value>";
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/XMLRPCBoolParameter.php b/engine/classes/XMLRPCBoolParameter.php
index c53d9ad14..607841cb8 100644
--- a/engine/classes/XMLRPCBoolParameter.php
+++ b/engine/classes/XMLRPCBoolParameter.php
@@ -27,4 +27,4 @@ class XMLRPCBoolParameter extends XMLRPCParameter {
$code = ($this->value) ? "1" : "0";
return "<value><boolean>{$code}</boolean></value>";
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/XMLRPCCall.php b/engine/classes/XMLRPCCall.php
index 3b33f3cee..fd28f1e3e 100644
--- a/engine/classes/XMLRPCCall.php
+++ b/engine/classes/XMLRPCCall.php
@@ -18,7 +18,7 @@ class XMLRPCCall {
* @param string $xml XML
*/
function __construct($xml) {
- $this->_parse($xml);
+ $this->parse($xml);
}
/**
@@ -45,7 +45,7 @@ class XMLRPCCall {
*
* @return void
*/
- private function _parse($xml) {
+ private function parse($xml) {
$xml = xml_to_object($xml);
// sanity check
@@ -59,4 +59,4 @@ class XMLRPCCall {
// parameters
$this->params = $xml->children[1]->children;
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/XMLRPCDateParameter.php b/engine/classes/XMLRPCDateParameter.php
index 35e02fb37..93bbbd8f5 100644
--- a/engine/classes/XMLRPCDateParameter.php
+++ b/engine/classes/XMLRPCDateParameter.php
@@ -30,4 +30,4 @@ class XMLRPCDateParameter extends XMLRPCParameter {
$value = date('c', $this->value);
return "<value><dateTime.iso8601>{$value}</dateTime.iso8601></value>";
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/XMLRPCErrorResponse.php b/engine/classes/XMLRPCErrorResponse.php
index c3dbd0373..425c075cc 100644
--- a/engine/classes/XMLRPCErrorResponse.php
+++ b/engine/classes/XMLRPCErrorResponse.php
@@ -33,4 +33,4 @@ class XMLRPCErrorResponse extends XMLRPCResponse {
public function __toString() {
return "<methodResponse><fault><value>{$this->parameters[0]}</value></fault></methodResponse>";
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/XMLRPCParameter.php b/engine/classes/XMLRPCParameter.php
index 5fac33201..ffbad8082 100644
--- a/engine/classes/XMLRPCParameter.php
+++ b/engine/classes/XMLRPCParameter.php
@@ -13,4 +13,4 @@ abstract class XMLRPCParameter {
*/
function __construct() { }
-} \ No newline at end of file
+}
diff --git a/engine/classes/XMLRPCResponse.php b/engine/classes/XMLRPCResponse.php
index 2a0319431..a6256d385 100644
--- a/engine/classes/XMLRPCResponse.php
+++ b/engine/classes/XMLRPCResponse.php
@@ -68,4 +68,4 @@ abstract class XMLRPCResponse {
public function addBoolean($value) {
$this->addParameter(new XMLRPCBoolParameter($value));
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/XMLRPCStructParameter.php b/engine/classes/XMLRPCStructParameter.php
index 754c92616..694ddf5df 100644
--- a/engine/classes/XMLRPCStructParameter.php
+++ b/engine/classes/XMLRPCStructParameter.php
@@ -52,4 +52,4 @@ class XMLRPCStructParameter extends XMLRPCParameter {
return "<value><struct>$params</struct></value>";
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/XMLRPCSuccessResponse.php b/engine/classes/XMLRPCSuccessResponse.php
index 2f452ef42..e02e82c5c 100644
--- a/engine/classes/XMLRPCSuccessResponse.php
+++ b/engine/classes/XMLRPCSuccessResponse.php
@@ -19,4 +19,4 @@ class XMLRPCSuccessResponse extends XMLRPCResponse {
return "<methodResponse><params>$params</params></methodResponse>";
}
-} \ No newline at end of file
+}
diff --git a/engine/classes/XmlElement.php b/engine/classes/XmlElement.php
index 64d54e6b0..280bba664 100644
--- a/engine/classes/XmlElement.php
+++ b/engine/classes/XmlElement.php
@@ -17,4 +17,4 @@ class XmlElement {
/** Child elements */
public $children;
-}; \ No newline at end of file
+};