aboutsummaryrefslogtreecommitdiff
path: root/engine
diff options
context:
space:
mode:
authorSem <sembrestels@riseup.net>2013-02-13 01:25:07 +0100
committerSem <sembrestels@riseup.net>2013-02-13 01:25:07 +0100
commit5aff5b3913fbb6226d8fb3162453c58888fba38d (patch)
tree2bf8fe649d8c9d09a3be03154c4c669bccfbc54c /engine
parent3ed289b03fa3d851fd7fffbc0441ebc9b5e98310 (diff)
parent8d3a7ab1755829c6e070a038d6b33e326de8fc8f (diff)
downloadelgg-5aff5b3913fbb6226d8fb3162453c58888fba38d.tar.gz
elgg-5aff5b3913fbb6226d8fb3162453c58888fba38d.tar.bz2
Merge tag '1.8.13' of git://github.com/Elgg/Elgg
Elgg 1.8.13 release
Diffstat (limited to 'engine')
-rw-r--r--engine/classes/ElggAttributeLoader.php199
-rw-r--r--engine/classes/ElggAutoP.php309
-rw-r--r--engine/classes/ElggDiskFilestore.php10
-rw-r--r--engine/classes/ElggGroup.php37
-rw-r--r--engine/classes/ElggGroupItemVisibility.php93
-rw-r--r--engine/classes/ElggMenuBuilder.php8
-rw-r--r--engine/classes/ElggMenuItem.php2
-rw-r--r--engine/classes/ElggObject.php39
-rw-r--r--engine/classes/ElggPlugin.php63
-rw-r--r--engine/classes/ElggPluginManifest.php2
-rw-r--r--engine/classes/ElggPluginManifestParser.php6
-rw-r--r--engine/classes/ElggSession.php14
-rw-r--r--engine/classes/ElggSite.php42
-rw-r--r--engine/classes/ElggStaticVariableCache.php4
-rw-r--r--engine/classes/ElggUser.php36
-rw-r--r--engine/classes/ElggXMLElement.php115
-rw-r--r--engine/classes/IncompleteEntityException.php10
-rw-r--r--engine/lib/access.php153
-rw-r--r--engine/lib/admin.php3
-rw-r--r--engine/lib/annotations.php11
-rw-r--r--engine/lib/elgglib.php22
-rw-r--r--engine/lib/entities.php106
-rw-r--r--engine/lib/group.php57
-rw-r--r--engine/lib/navigation.php26
-rw-r--r--engine/lib/objects.php1
-rw-r--r--engine/lib/output.php90
-rw-r--r--engine/lib/pageowner.php21
-rw-r--r--engine/lib/plugins.php1
-rw-r--r--engine/lib/private_settings.php5
-rw-r--r--engine/lib/sites.php1
-rw-r--r--engine/lib/upgrade.php55
-rw-r--r--engine/lib/upgrades/2011010101.php2
-rw-r--r--engine/lib/users.php1
-rw-r--r--engine/lib/views.php9
-rw-r--r--engine/lib/xml.php38
-rw-r--r--engine/tests/api/access_collections.php22
-rw-r--r--engine/tests/api/output.php74
-rw-r--r--engine/tests/test_files/output/autop/block-a.exp.norun.html6
-rw-r--r--engine/tests/test_files/output/autop/block-a.in.norun.html9
-rw-r--r--engine/tests/test_files/output/autop/domdoc_exp.html46
-rw-r--r--engine/tests/test_files/output/autop/domdoc_in.html80
-rw-r--r--engine/tests/test_files/output/autop/typical-post.exp.html84
-rw-r--r--engine/tests/test_files/output/autop/typical-post.in.html89
-rw-r--r--engine/tests/test_files/output/autop/wp-welcome.exp.html22
-rw-r--r--engine/tests/test_files/output/autop/wp-welcome.in.html25
-rw-r--r--engine/tests/test_files/output/autop/wpautop-fails.exp.html31
-rw-r--r--engine/tests/test_files/output/autop/wpautop-fails.in.html41
-rw-r--r--engine/tests/test_files/output/autop/wysiwyg-test.exp.html51
-rw-r--r--engine/tests/test_files/output/autop/wysiwyg-test.in.html79
49 files changed, 1860 insertions, 390 deletions
diff --git a/engine/classes/ElggAttributeLoader.php b/engine/classes/ElggAttributeLoader.php
new file mode 100644
index 000000000..602bb8bae
--- /dev/null
+++ b/engine/classes/ElggAttributeLoader.php
@@ -0,0 +1,199 @@
+<?php
+
+/**
+ * Loads ElggEntity attributes from DB or validates those passed in via constructor
+ *
+ * @access private
+ */
+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 = '';
+
+ /**
+ * @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);
+ }
+
+ protected function isMissingPrimaries($row) {
+ return array_diff(self::$primary_attr_names, array_keys($row)) !== array();
+ }
+
+ protected function isMissingSecondaries($row) {
+ return array_diff($this->secondary_attr_names, array_keys($row)) !== array();
+ }
+
+ 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) {
+ $ignoring_access = elgg_set_ignore_access();
+ }
+ $fetched = (array) call_user_func($this->primary_loader, $row['guid']);
+ if (!$this->requires_access_control) {
+ 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['guid'] = (int) $row['guid'];
+ return $row;
+ }
+ throw new IncompleteEntityException("Secondary loader failed to return row for {$row['guid']}");
+ }
+ $row = array_merge($row, $fetched);
+ }
+ }
+
+ // loading complete: re-check missing and check type
+ if (($was_missing_primaries && $this->isMissingPrimaries($row))
+ || ($was_missing_secondaries && $this->isMissingSecondaries($row))) {
+ throw new LogicException('Attribute loaders failed to return proper attributes');
+ }
+
+ // guid needs to be an int http://trac.elgg.org/ticket/4111
+ $row['guid'] = (int) $row['guid'];
+
+ return $row;
+ }
+}
diff --git a/engine/classes/ElggAutoP.php b/engine/classes/ElggAutoP.php
new file mode 100644
index 000000000..89d77e583
--- /dev/null
+++ b/engine/classes/ElggAutoP.php
@@ -0,0 +1,309 @@
+<?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.
+ */
+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';
+
+ protected $_unique = '';
+
+ 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);
+
+ if (!$this->_doc->loadHTML("<html><meta http-equiv='content-type' "
+ . "content='text/html; charset={$this->encoding}'><body>{$html}</body>"
+ . "</html>")) {
+ return false;
+ }
+
+ $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();
+
+ // 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
+
+ if (!$this->_doc->loadHTML($html)) {
+ return false;
+ }
+ // 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) {
+ $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) {
+ // strip w/ preg_replace later (faster than moving nodes out)
+ $autop->setAttribute("r", "1");
+ }
+ }
+
+ // remove a single AUTOP inside certain elements
+ foreach ($this->_xpath->query('//div') as $el) {
+ $autops = $this->_xpath->query('./autop', $el);
+ if ($autops->length === 1) {
+ // strip w/ preg_replace later (faster than moving nodes out)
+ $autops->item(0)->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
+ */
+ protected function _addParagraphs(DOMElement $el) {
+ // no need to recurse, 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) {
+ $elName = $node->nodeName;
+ }
+ $isBlock = ($isElement && in_array($elName, $this->_blocks));
+
+ if ($alterInline) {
+ $isInline = $isElement && ! $isBlock;
+ $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/ElggDiskFilestore.php b/engine/classes/ElggDiskFilestore.php
index f00376481..7aace43ba 100644
--- a/engine/classes/ElggDiskFilestore.php
+++ b/engine/classes/ElggDiskFilestore.php
@@ -200,18 +200,18 @@ class ElggDiskFilestore extends ElggFilestore {
* @return string The full path of where the file is stored
*/
public function getFilenameOnFilestore(ElggFile $file) {
- $owner = $file->getOwnerEntity();
- if (!$owner) {
- $owner = elgg_get_logged_in_user_entity();
+ $owner_guid = $file->getOwnerGuid();
+ if (!$owner_guid) {
+ $owner_guid = elgg_get_logged_in_user_guid();
}
- if (!$owner) {
+ 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();
+ return $this->dir_root . $this->makefileMatrix($owner_guid) . $file->getFilename();
}
/**
diff --git a/engine/classes/ElggGroup.php b/engine/classes/ElggGroup.php
index 121186196..ea257f368 100644
--- a/engine/classes/ElggGroup.php
+++ b/engine/classes/ElggGroup.php
@@ -324,37 +324,18 @@ class ElggGroup extends ElggEntity
* @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';
- // Only work with GUID from here
- if ($guid instanceof stdClass) {
- $guid = $guid->guid;
- }
-
- // Check the type
- if ($this->attributes['type'] != 'group') {
- $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class()));
- throw new InvalidClassException($msg);
- }
-
- // Load missing data
- $row = get_group_entity_as_row($guid);
- if (($row) && (!$this->isFullyLoaded())) {
- // If $row isn't a cached copy then increment the counter
- $this->attributes['tables_loaded']++;
- }
-
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach ($objarray as $key => $value) {
- $this->attributes[$key] = $value;
+ $attrs = $attr_loader->getRequiredAttributes($guid);
+ if (!$attrs) {
+ return false;
}
- // guid needs to be an int http://trac.elgg.org/ticket/4111
- $this->attributes['guid'] = (int)$this->attributes['guid'];
+ $this->attributes = $attrs;
+ $this->attributes['tables_loaded'] = 2;
+ cache_entity($this);
return true;
}
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/ElggMenuBuilder.php b/engine/classes/ElggMenuBuilder.php
index f43599999..d9a704dd9 100644
--- a/engine/classes/ElggMenuBuilder.php
+++ b/engine/classes/ElggMenuBuilder.php
@@ -205,7 +205,7 @@ class ElggMenuBuilder {
// sort each section
foreach ($this->menu as $index => $section) {
foreach ($section as $key => $node) {
- $section[$key]->original_order = $key;
+ $section[$key]->setData('original_order', $key);
}
usort($section, $sort_callback);
$this->menu[$index] = $section;
@@ -240,7 +240,7 @@ class ElggMenuBuilder {
$result = strnatcmp($at, $bt);
if ($result === 0) {
- return $a->original_order - $b->original_order;
+ return $a->getData('original_order') - $b->getData('original_order');
}
return $result;
}
@@ -258,7 +258,7 @@ class ElggMenuBuilder {
$result = strcmp($an, $bn);
if ($result === 0) {
- return $a->original_order - $b->original_order;
+ return $a->getData('original_order') - $b->getData('original_order');
}
return $result;
}
@@ -275,7 +275,7 @@ class ElggMenuBuilder {
$bw = $b->getWeight();
if ($aw == $bw) {
- return $a->original_order - $b->original_order;
+ return $a->getData('original_order') - $b->getData('original_order');
}
return $aw - $bw;
}
diff --git a/engine/classes/ElggMenuItem.php b/engine/classes/ElggMenuItem.php
index fe25f3ddd..81ce6c099 100644
--- a/engine/classes/ElggMenuItem.php
+++ b/engine/classes/ElggMenuItem.php
@@ -543,7 +543,7 @@ class ElggMenuItem {
*/
public function sortChildren($sortFunction) {
foreach ($this->data['children'] as $key => $node) {
- $this->data['children'][$key]->original_order = $key;
+ $this->data['children'][$key]->data['original_order'] = $key;
}
usort($this->data['children'], $sortFunction);
}
diff --git a/engine/classes/ElggObject.php b/engine/classes/ElggObject.php
index fa6296c8c..6263f84f6 100644
--- a/engine/classes/ElggObject.php
+++ b/engine/classes/ElggObject.php
@@ -99,37 +99,18 @@ class ElggObject extends ElggEntity {
* @throws InvalidClassException
*/
protected function load($guid) {
- // Load data from entity table if needed
- if (!parent::load($guid)) {
- return false;
- }
+ $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';
- // Only work with GUID from here
- if ($guid instanceof stdClass) {
- $guid = $guid->guid;
- }
-
- // Check the type
- if ($this->attributes['type'] != 'object') {
- $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class()));
- throw new InvalidClassException($msg);
- }
-
- // Load missing data
- $row = get_object_entity_as_row($guid);
- if (($row) && (!$this->isFullyLoaded())) {
- // If $row isn't a cached copy then increment the counter
- $this->attributes['tables_loaded']++;
- }
-
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach ($objarray as $key => $value) {
- $this->attributes[$key] = $value;
+ $attrs = $attr_loader->getRequiredAttributes($guid);
+ if (!$attrs) {
+ return false;
}
- // guid needs to be an int http://trac.elgg.org/ticket/4111
- $this->attributes['guid'] = (int)$this->attributes['guid'];
+ $this->attributes = $attrs;
+ $this->attributes['tables_loaded'] = 2;
+ cache_entity($this);
return true;
}
@@ -149,7 +130,7 @@ 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'));
+ $this->get('description'));
}
/**
diff --git a/engine/classes/ElggPlugin.php b/engine/classes/ElggPlugin.php
index c6ce2905f..8f71b79a8 100644
--- a/engine/classes/ElggPlugin.php
+++ b/engine/classes/ElggPlugin.php
@@ -82,64 +82,6 @@ class ElggPlugin extends ElggObject {
}
/**
- * Overridden from ElggEntity and ElggObject::load(). Core always inits plugins with
- * a query joined to the objects_entity table, so all the info is there.
- *
- * @param mixed $guid GUID of an ElggObject or the stdClass object from entities table
- *
- * @return bool
- * @throws InvalidClassException
- */
- protected function load($guid) {
-
- $expected_attributes = $this->attributes;
- unset($expected_attributes['tables_split']);
- unset($expected_attributes['tables_loaded']);
-
- // this was loaded with a full join
- $needs_loaded = false;
-
- if ($guid instanceof stdClass) {
- $row = (array) $guid;
- $missing_attributes = array_diff_key($expected_attributes, $row);
- if ($missing_attributes) {
- $needs_loaded = true;
- $guid = $row['guid'];
- } else {
- $this->attributes = $row;
- }
- } else {
- $needs_loaded = true;
- }
-
- if ($needs_loaded) {
- $entity = (array) get_entity_as_row($guid);
- $object = (array) get_object_entity_as_row($guid);
-
- if (!$entity || !$object) {
- return false;
- }
-
- $this->attributes = array_merge($this->attributes, $entity, $object);
- }
-
- $this->attributes['tables_loaded'] = 2;
-
- // Check the type
- if ($this->attributes['type'] != 'object') {
- $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class()));
- throw new InvalidClassException($msg);
- }
-
- // guid needs to be an int http://trac.elgg.org/ticket/4111
- $this->attributes['guid'] = (int)$this->attributes['guid'];
-
- cache_entity($this);
-
- return true;
- }
-
- /**
* Save the plugin object. Make sure required values exist.
*
* @see ElggObject::save()
@@ -361,10 +303,7 @@ class ElggPlugin extends ElggObject {
$return = array();
foreach ($private_settings as $setting) {
- $name = substr($setting->name, $ps_prefix_len);
- $value = $setting->value;
-
- $return[$name] = $value;
+ $return[$setting->name] = $setting->value;
}
return $return;
diff --git a/engine/classes/ElggPluginManifest.php b/engine/classes/ElggPluginManifest.php
index a4f5bb95d..6912c2b08 100644
--- a/engine/classes/ElggPluginManifest.php
+++ b/engine/classes/ElggPluginManifest.php
@@ -130,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) == '<') {
diff --git a/engine/classes/ElggPluginManifestParser.php b/engine/classes/ElggPluginManifestParser.php
index b0480d4d8..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;
}
diff --git a/engine/classes/ElggSession.php b/engine/classes/ElggSession.php
index 13a33736c..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);
}
}
diff --git a/engine/classes/ElggSite.php b/engine/classes/ElggSite.php
index 401939005..1fe49b85c 100644
--- a/engine/classes/ElggSite.php
+++ b/engine/classes/ElggSite.php
@@ -117,37 +117,18 @@ class ElggSite extends ElggEntity {
* @throws InvalidClassException
*/
protected function load($guid) {
- // Test to see if we have the generic stuff
- if (!parent::load($guid)) {
- return false;
- }
-
- // Only work with GUID from here
- if ($guid instanceof stdClass) {
- $guid = $guid->guid;
- }
-
- // Check the type
- if ($this->attributes['type'] != 'site') {
- $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class()));
- throw new InvalidClassException($msg);
- }
+ $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';
- // Load missing data
- $row = get_site_entity_as_row($guid);
- if (($row) && (!$this->isFullyLoaded())) {
- // If $row isn't a cached copy then increment the counter
- $this->attributes['tables_loaded']++;
- }
-
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach ($objarray as $key => $value) {
- $this->attributes[$key] = $value;
+ $attrs = $attr_loader->getRequiredAttributes($guid);
+ if (!$attrs) {
+ return false;
}
- // guid needs to be an int http://trac.elgg.org/ticket/4111
- $this->attributes['guid'] = (int)$this->attributes['guid'];
+ $this->attributes = $attrs;
+ $this->attributes['tables_loaded'] = 2;
+ cache_entity($this);
return true;
}
@@ -381,6 +362,11 @@ class ElggSite extends ElggEntity {
public function checkWalledGarden() {
global $CONFIG;
+ // command line calls should not invoke the walled garden check
+ if (PHP_SAPI === 'cli') {
+ return;
+ }
+
if ($CONFIG->walled_garden) {
if ($CONFIG->default_access == ACCESS_PUBLIC) {
$CONFIG->default_access = ACCESS_LOGGED_IN;
diff --git a/engine/classes/ElggStaticVariableCache.php b/engine/classes/ElggStaticVariableCache.php
index 787d35a32..17d849400 100644
--- a/engine/classes/ElggStaticVariableCache.php
+++ b/engine/classes/ElggStaticVariableCache.php
@@ -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.
+ * @note namespaces of the same name are shared!
*/
function __construct($namespace = 'default') {
$this->setNamespace($namespace);
diff --git a/engine/classes/ElggUser.php b/engine/classes/ElggUser.php
index d7bb89265..6c1cdc1de 100644
--- a/engine/classes/ElggUser.php
+++ b/engine/classes/ElggUser.php
@@ -106,37 +106,17 @@ class ElggUser extends ElggEntity
* @return bool
*/
protected function load($guid) {
- // Test to see if we have the generic stuff
- if (!parent::load($guid)) {
- return false;
- }
+ $attr_loader = new ElggAttributeLoader(get_class(), 'user', $this->attributes);
+ $attr_loader->secondary_loader = 'get_user_entity_as_row';
- // Only work with GUID from here
- if ($guid instanceof stdClass) {
- $guid = $guid->guid;
- }
-
- // Check the type
- if ($this->attributes['type'] != 'user') {
- $msg = elgg_echo('InvalidClassException:NotValidElggStar', array($guid, get_class()));
- throw new InvalidClassException($msg);
- }
-
- // Load missing data
- $row = get_user_entity_as_row($guid);
- if (($row) && (!$this->isFullyLoaded())) {
- // If $row isn't a cached copy then increment the counter
- $this->attributes['tables_loaded']++;
- }
-
- // Now put these into the attributes array as core values
- $objarray = (array) $row;
- foreach ($objarray as $key => $value) {
- $this->attributes[$key] = $value;
+ $attrs = $attr_loader->getRequiredAttributes($guid);
+ if (!$attrs) {
+ return false;
}
- // guid needs to be an int http://trac.elgg.org/ticket/4111
- $this->attributes['guid'] = (int)$this->attributes['guid'];
+ $this->attributes = $attrs;
+ $this->attributes['tables_loaded'] = 2;
+ cache_entity($this);
return true;
}
diff --git a/engine/classes/ElggXMLElement.php b/engine/classes/ElggXMLElement.php
new file mode 100644
index 000000000..65a13912c
--- /dev/null
+++ b/engine/classes/ElggXMLElement.php
@@ -0,0 +1,115 @@
+<?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 {
+ $this->_element = new SimpleXMLElement($xml);
+ }
+ }
+
+ /**
+ * @return string The name of the element
+ */
+ public function getName() {
+ return $this->_element->getName();
+ }
+
+ /**
+ * @return array: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 array:ElggXMLElement Child elements
+ */
+ public function getChildren() {
+ $children = $this->_element->children();
+ $result = array();
+ foreach ($children as $val) {
+ $result[] = new ElggXMLElement($val);
+ }
+
+ return $result;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+} \ No newline at end of file
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/lib/access.php b/engine/lib/access.php
index 3b2b7aeaa..f7d3bf7ea 100644
--- a/engine/lib/access.php
+++ b/engine/lib/access.php
@@ -12,6 +12,26 @@
*/
/**
+ * Return an ElggCache static variable cache for the access caches
+ *
+ * @staticvar ElggStaticVariableCache $access_cache
+ * @return \ElggStaticVariableCache
+ * @access private
+ */
+function _elgg_get_access_cache() {
+ /**
+ * A default filestore cache using the dataroot.
+ */
+ static $access_cache;
+
+ if (!$access_cache) {
+ $access_cache = new ElggStaticVariableCache('access');
+ }
+
+ return $access_cache;
+}
+
+/**
* Return a string of access_ids for $user_id appropriate for inserting into an SQL IN clause.
*
* @uses get_access_array
@@ -29,10 +49,10 @@
*/
function get_access_list($user_id = 0, $site_id = 0, $flush = false) {
global $CONFIG, $init_finished;
- static $access_list;
-
- if (!isset($access_list)) {
- $access_list = array();
+ $cache = _elgg_get_access_cache();
+
+ if ($flush) {
+ $cache->clear();
}
if ($user_id == 0) {
@@ -45,20 +65,20 @@ function get_access_list($user_id = 0, $site_id = 0, $flush = false) {
$user_id = (int) $user_id;
$site_id = (int) $site_id;
- if (isset($access_list[$user_id]) && $flush == false) {
- return $access_list[$user_id];
- }
+ $hash = $user_id . $site_id . 'get_access_list';
- $access = "(" . implode(",", get_access_array($user_id, $site_id, $flush)) . ")";
+ if ($cache[$hash]) {
+ return $cache[$hash];
+ }
+
+ $access_array = get_access_array($user_id, $site_id, $flush);
+ $access = "(" . implode(",", $access_array) . ")";
- // only cache if done with init and access is enabled (unless admin user)
- // session is loaded before init is finished, so don't need to check for user session
- if ($init_finished && (elgg_is_admin_logged_in() || !elgg_get_ignore_access())) {
- $access_list[$user_id] = $access;
- return $access_list[$user_id];
- } else {
- return $access;
+ if ($init_finished) {
+ $cache[$hash] = $access;
}
+
+ return $access;
}
/**
@@ -86,9 +106,11 @@ function get_access_list($user_id = 0, $site_id = 0, $flush = false) {
function get_access_array($user_id = 0, $site_id = 0, $flush = false) {
global $CONFIG, $init_finished;
- // @todo everything from the db is cached.
- // this cache might be redundant. But db cache is flushed on every db write.
- static $access_array = array();
+ $cache = _elgg_get_access_cache();
+
+ if ($flush) {
+ $cache->clear();
+ }
if ($user_id == 0) {
$user_id = elgg_get_logged_in_user_guid();
@@ -101,35 +123,41 @@ function get_access_array($user_id = 0, $site_id = 0, $flush = false) {
$user_id = (int) $user_id;
$site_id = (int) $site_id;
- if (empty($access_array[$user_id]) || $flush == true) {
- $tmp_access_array = array(ACCESS_PUBLIC);
+ $hash = $user_id . $site_id . 'get_access_array';
+
+ if ($cache[$hash]) {
+ $access_array = $cache[$hash];
+ } else {
+ $access_array = array(ACCESS_PUBLIC);
// The following can only return sensible data if the user is logged in.
if (elgg_is_logged_in()) {
- $tmp_access_array[] = ACCESS_LOGGED_IN;
+ $access_array[] = ACCESS_LOGGED_IN;
// Get ACL memberships
$query = "SELECT am.access_collection_id"
. " FROM {$CONFIG->dbprefix}access_collection_membership am"
. " LEFT JOIN {$CONFIG->dbprefix}access_collections ag ON ag.id = am.access_collection_id"
- . " WHERE am.user_guid = {$user_id} AND (ag.site_guid = {$site_id} OR ag.site_guid = 0)";
+ . " WHERE am.user_guid = $user_id AND (ag.site_guid = $site_id OR ag.site_guid = 0)";
- if ($collections = get_data($query)) {
+ $collections = get_data($query);
+ if ($collections) {
foreach ($collections as $collection) {
if (!empty($collection->access_collection_id)) {
- $tmp_access_array[] = (int)$collection->access_collection_id;
+ $access_array[] = (int)$collection->access_collection_id;
}
}
}
// Get ACLs owned.
$query = "SELECT ag.id FROM {$CONFIG->dbprefix}access_collections ag ";
- $query .= "WHERE ag.owner_guid = {$user_id} AND (ag.site_guid = {$site_id} OR ag.site_guid = 0)";
+ $query .= "WHERE ag.owner_guid = $user_id AND (ag.site_guid = $site_id OR ag.site_guid = 0)";
- if ($collections = get_data($query)) {
+ $collections = get_data($query);
+ if ($collections) {
foreach ($collections as $collection) {
if (!empty($collection->id)) {
- $tmp_access_array[] = (int)$collection->id;
+ $access_array[] = (int)$collection->id;
}
}
}
@@ -137,21 +165,21 @@ function get_access_array($user_id = 0, $site_id = 0, $flush = false) {
$ignore_access = elgg_check_access_overrides($user_id);
if ($ignore_access == true) {
- $tmp_access_array[] = ACCESS_PRIVATE;
+ $access_array[] = ACCESS_PRIVATE;
}
+ }
- // only cache if done with init and access is enabled (unless admin user)
- // session is loaded before init is finished, so don't need to check for user session
- if ($init_finished && (elgg_is_admin_logged_in() || !elgg_get_ignore_access())) {
- $access_array[$user_id] = $tmp_access_array;
- }
+ if ($init_finished) {
+ $cache[$hash] = $access_array;
}
- } else {
- $tmp_access_array = $access_array[$user_id];
}
- $options = array('user_id' => $user_id, 'site_id' => $site_id);
- return elgg_trigger_plugin_hook('access:collections:read', 'user', $options, $tmp_access_array);
+ $options = array(
+ 'user_id' => $user_id,
+ 'site_id' => $site_id
+ );
+
+ return elgg_trigger_plugin_hook('access:collections:read', 'user', $options, $access_array);
}
/**
@@ -397,9 +425,12 @@ function has_access_to_entity($entity, $user = null) {
* @link http://docs.elgg.org/Access
*/
function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) {
- global $CONFIG;
- //@todo this is probably not needed since caching happens at the DB level.
- static $access_array;
+ global $CONFIG, $init_finished;
+ $cache = _elgg_get_access_cache();
+
+ if ($flush) {
+ $cache->clear();
+ }
if ($user_id == 0) {
$user_id = elgg_get_logged_in_user_guid();
@@ -412,37 +443,41 @@ function get_write_access_array($user_id = 0, $site_id = 0, $flush = false) {
$user_id = (int) $user_id;
$site_id = (int) $site_id;
- if (empty($access_array[$user_id]) || $flush == true) {
- $query = "SELECT ag.* FROM {$CONFIG->dbprefix}access_collections ag ";
- $query .= " WHERE (ag.site_guid = {$site_id} OR ag.site_guid = 0)";
- $query .= " AND (ag.owner_guid = {$user_id})";
- // ACCESS_PRIVATE through ACCESS_PUBLIC take 0 through 2
- // @todo this AND clause is unnecessary because of id starts at 3 for table
- $query .= " AND ag.id >= 3";
+ $hash = $user_id . $site_id . 'get_write_access_array';
- $tmp_access_array = array(
+ if ($cache[$hash]) {
+ $access_array = $cache[$hash];
+ } else {
+ // @todo is there such a thing as public write access?
+ $access_array = array(
ACCESS_PRIVATE => elgg_echo("PRIVATE"),
ACCESS_FRIENDS => elgg_echo("access:friends:label"),
ACCESS_LOGGED_IN => elgg_echo("LOGGED_IN"),
ACCESS_PUBLIC => elgg_echo("PUBLIC")
);
+
+ $query = "SELECT ag.* FROM {$CONFIG->dbprefix}access_collections ag ";
+ $query .= " WHERE (ag.site_guid = $site_id OR ag.site_guid = 0)";
+ $query .= " AND (ag.owner_guid = $user_id)";
+
$collections = get_data($query);
if ($collections) {
foreach ($collections as $collection) {
- $tmp_access_array[$collection->id] = $collection->name;
+ $access_array[$collection->id] = $collection->name;
}
}
- $access_array[$user_id] = $tmp_access_array;
- } else {
- $tmp_access_array = $access_array[$user_id];
+ if ($init_finished) {
+ $cache[$hash] = $access_array;
+ }
}
- $options = array('user_id' => $user_id, 'site_id' => $site_id);
- $tmp_access_array = elgg_trigger_plugin_hook('access:collections:write', 'user',
- $options, $tmp_access_array);
-
- return $tmp_access_array;
+ $options = array(
+ 'user_id' => $user_id,
+ 'site_id' => $site_id
+ );
+ return elgg_trigger_plugin_hook('access:collections:write', 'user',
+ $options, $access_array);
}
/**
@@ -871,6 +906,8 @@ function get_readable_access_level($entity_access_id) {
* @tip Use this to access entities in automated scripts
* when no user is logged in.
*
+ * @note This clears the access cache.
+ *
* @warning This will not show disabled entities.
* Use {@link access_show_hidden_entities()} to access disabled entities.
*
@@ -882,6 +919,8 @@ function get_readable_access_level($entity_access_id) {
* @see elgg_get_ignore_access()
*/
function elgg_set_ignore_access($ignore = true) {
+ $cache = _elgg_get_access_cache();
+ $cache->clear();
$elgg_access = elgg_get_access_object();
return $elgg_access->setIgnoreAccess($ignore);
}
diff --git a/engine/lib/admin.php b/engine/lib/admin.php
index 3f23f079c..35ab5599d 100644
--- a/engine/lib/admin.php
+++ b/engine/lib/admin.php
@@ -233,6 +233,7 @@ function admin_init() {
elgg_register_action('admin/site/update_basic', '', 'admin');
elgg_register_action('admin/site/update_advanced', '', 'admin');
elgg_register_action('admin/site/flush_cache', '', 'admin');
+ elgg_register_action('admin/site/unlock_upgrade', '', 'admin');
elgg_register_action('admin/menu/save', '', 'admin');
@@ -422,7 +423,7 @@ function admin_pagesetup() {
elgg_register_menu_item('admin_footer', array(
'name' => 'community_forums',
'text' => elgg_echo('admin:footer:community_forums'),
- 'href' => 'http://community.elgg.org/pg/groups/world/',
+ 'href' => 'http://community.elgg.org/groups/all/',
));
elgg_register_menu_item('admin_footer', array(
diff --git a/engine/lib/annotations.php b/engine/lib/annotations.php
index 2036ccd61..3b9f84703 100644
--- a/engine/lib/annotations.php
+++ b/engine/lib/annotations.php
@@ -316,8 +316,6 @@ function elgg_list_annotations($options) {
*
* annotation_owner_guids => NULL|ARR guids for annotaiton owners
*
- * annotation_ids => NULL|ARR Annotation IDs
- *
* @return mixed If count, int. If not count, array. false on errors.
* @since 1.7.0
*/
@@ -336,8 +334,6 @@ function elgg_get_entities_from_annotations(array $options = array()) {
'annotation_owner_guids' => ELGG_ENTITIES_ANY_VALUE,
- 'annotation_ids' => ELGG_ENTITIES_ANY_VALUE,
-
'order_by' => 'maxtime desc',
'group_by' => 'a.entity_guid'
);
@@ -345,12 +341,13 @@ function elgg_get_entities_from_annotations(array $options = array()) {
$options = array_merge($defaults, $options);
$singulars = array('annotation_name', 'annotation_value',
- 'annotation_name_value_pair', 'annotation_owner_guid', 'annotation_id');
+ 'annotation_name_value_pair', 'annotation_owner_guid');
$options = elgg_normalise_plural_options_array($options, $singulars);
+ $options = elgg_entities_get_metastrings_options('annotation', $options);
- if (!$options = elgg_entities_get_metastrings_options('annotation', $options)) {
- return FALSE;
+ if (!$options) {
+ return false;
}
// special sorting for annotations
diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php
index dd3cba25d..540605876 100644
--- a/engine/lib/elgglib.php
+++ b/engine/lib/elgglib.php
@@ -710,9 +710,12 @@ function elgg_register_event_handler($event, $object_type, $callback, $priority
*/
function elgg_unregister_event_handler($event, $object_type, $callback) {
global $CONFIG;
- foreach ($CONFIG->events[$event][$object_type] as $key => $event_callback) {
- if ($event_callback == $callback) {
- unset($CONFIG->events[$event][$object_type][$key]);
+
+ if (isset($CONFIG->events[$event]) && isset($CONFIG->events[$event][$object_type])) {
+ foreach ($CONFIG->events[$event][$object_type] as $key => $event_callback) {
+ if ($event_callback == $callback) {
+ unset($CONFIG->events[$event][$object_type][$key]);
+ }
}
}
}
@@ -889,9 +892,12 @@ function elgg_register_plugin_hook_handler($hook, $type, $callback, $priority =
*/
function elgg_unregister_plugin_hook_handler($hook, $entity_type, $callback) {
global $CONFIG;
- foreach ($CONFIG->hooks[$hook][$entity_type] as $key => $hook_callback) {
- if ($hook_callback == $callback) {
- unset($CONFIG->hooks[$hook][$entity_type][$key]);
+
+ if (isset($CONFIG->hooks[$hook]) && isset($CONFIG->hooks[$hook][$entity_type])) {
+ foreach ($CONFIG->hooks[$hook][$entity_type] as $key => $hook_callback) {
+ if ($hook_callback == $callback) {
+ unset($CONFIG->hooks[$hook][$entity_type][$key]);
+ }
}
}
}
@@ -1073,8 +1079,8 @@ function _elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) {
case E_USER_WARNING :
case E_RECOVERABLE_ERROR: // (e.g. type hint violation)
- // check if the error wasn't suppressed by @-functionname
- if(error_reporting()){
+ // check if the error wasn't suppressed by the error control operator (@)
+ if (error_reporting()) {
error_log("PHP WARNING: $error");
}
break;
diff --git a/engine/lib/entities.php b/engine/lib/entities.php
index fda554388..ce736ce05 100644
--- a/engine/lib/entities.php
+++ b/engine/lib/entities.php
@@ -773,7 +773,13 @@ function get_entity($guid) {
}
}
- $new_entity = entity_row_to_elggstar($entity_row);
+ // don't let incomplete entities cause fatal exceptions
+ try {
+ $new_entity = entity_row_to_elggstar($entity_row);
+ } catch (IncompleteEntityException $e) {
+ return false;
+ }
+
if ($new_entity) {
cache_entity($new_entity);
}
@@ -1018,7 +1024,12 @@ function elgg_get_entities(array $options = array()) {
$query .= " LIMIT $offset, $limit";
}
- $dt = get_data($query, $options['callback']);
+ if ($options['callback'] === 'entity_row_to_elggstar') {
+ $dt = _elgg_fetch_entities_from_sql($query);
+ } else {
+ $dt = get_data($query, $options['callback']);
+ }
+
if ($dt) {
// populate entity and metadata caches
$guids = array();
@@ -1047,6 +1058,97 @@ function elgg_get_entities(array $options = array()) {
}
/**
+ * Return entities from an SQL query generated by elgg_get_entities.
+ *
+ * @param string $sql
+ * @return ElggEntity[]
+ *
+ * @access private
+ * @throws LogicException
+ */
+function _elgg_fetch_entities_from_sql($sql) {
+ static $plugin_subtype;
+ if (null === $plugin_subtype) {
+ $plugin_subtype = get_subtype_id('object', 'plugin');
+ }
+
+ // Keys are types, values are columns that, if present, suggest that the secondary
+ // table is already JOINed
+ $types_to_optimize = array(
+ 'object' => 'title',
+ 'user' => 'password',
+ 'group' => 'name',
+ );
+
+ $rows = get_data($sql);
+
+ // guids to look up in each type
+ $lookup_types = array();
+ // maps GUIDs to the $rows key
+ $guid_to_key = array();
+
+ if (isset($rows[0]->type, $rows[0]->subtype)
+ && $rows[0]->type === 'object'
+ && $rows[0]->subtype == $plugin_subtype) {
+ // Likely the entire resultset is plugins, which have already been optimized
+ // to JOIN the secondary table. In this case we allow retrieving from cache,
+ // but abandon the extra queries.
+ $types_to_optimize = array();
+ }
+
+ // First pass: use cache where possible, gather GUIDs that we're optimizing
+ foreach ($rows as $i => $row) {
+ if (empty($row->guid) || empty($row->type)) {
+ throw new LogicException('Entity row missing guid or type');
+ }
+ if ($entity = retrieve_cached_entity($row->guid)) {
+ $rows[$i] = $entity;
+ continue;
+ }
+ if (isset($types_to_optimize[$row->type])) {
+ // check if row already looks JOINed.
+ if (isset($row->{$types_to_optimize[$row->type]})) {
+ // Row probably already contains JOINed secondary table. Don't make another query just
+ // to pull data that's already there
+ continue;
+ }
+ $lookup_types[$row->type][] = $row->guid;
+ $guid_to_key[$row->guid] = $i;
+ }
+ }
+ // Do secondary queries and merge rows
+ if ($lookup_types) {
+ $dbprefix = elgg_get_config('dbprefix');
+ }
+ foreach ($lookup_types as $type => $guids) {
+ $set = "(" . implode(',', $guids) . ")";
+ $sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN $set";
+ $secondary_rows = get_data($sql);
+ if ($secondary_rows) {
+ foreach ($secondary_rows as $secondary_row) {
+ $key = $guid_to_key[$secondary_row->guid];
+ // cast to arrays to merge then cast back
+ $rows[$key] = (object)array_merge((array)$rows[$key], (array)$secondary_row);
+ }
+ }
+ }
+ // Second pass to finish conversion
+ foreach ($rows as $i => $row) {
+ if ($row instanceof ElggEntity) {
+ continue;
+ } else {
+ try {
+ $rows[$i] = entity_row_to_elggstar($row);
+ } catch (IncompleteEntityException $e) {
+ // don't let incomplete entities throw fatal errors
+ unset($rows[$i]);
+ }
+ }
+ }
+ return $rows;
+}
+
+/**
* Returns SQL where clause for type and subtype on main entity table
*
* @param string $table Entity table prefix as defined in SELECT...FROM entities $table
diff --git a/engine/lib/group.php b/engine/lib/group.php
index feb1f1e7f..5a38e1ea6 100644
--- a/engine/lib/group.php
+++ b/engine/lib/group.php
@@ -33,6 +33,7 @@ function get_group_entity_as_row($guid) {
* @param string $description Description
*
* @return bool
+ * @access private
*/
function create_group_entity($guid, $name, $description) {
global $CONFIG;
@@ -247,48 +248,42 @@ function get_users_membership($user_guid) {
}
/**
- * Checks access to a group.
+ * May the current user access item(s) on this page? If the page owner is a group,
+ * membership, visibility, and logged in status are taken into account.
*
* @param boolean $forward If set to true (default), will forward the page;
* if set to false, will return true or false.
*
- * @return true|false If $forward is set to false.
+ * @return bool If $forward is set to false.
*/
function group_gatekeeper($forward = true) {
- $allowed = true;
- $url = '';
-
- if ($group = elgg_get_page_owner_entity()) {
- if ($group instanceof ElggGroup) {
- $url = $group->getURL();
- if (!$group->isPublicMembership()) {
- // closed group so must be member or an admin
-
- if (!elgg_is_logged_in()) {
- $allowed = false;
- if ($forward == true) {
- $_SESSION['last_forward_from'] = current_page_url();
- register_error(elgg_echo('loggedinrequired'));
- forward('', 'login');
- }
- } else if (!$group->isMember(elgg_get_logged_in_user_entity())) {
- $allowed = false;
- }
- // Admin override
- if (elgg_is_admin_logged_in()) {
- $allowed = true;
- }
- }
- }
+ $page_owner_guid = elgg_get_page_owner_guid();
+ if (!$page_owner_guid) {
+ return true;
}
+ $visibility = ElggGroupItemVisibility::factory($page_owner_guid);
- if ($forward && $allowed == false) {
- register_error(elgg_echo('membershiprequired'));
- forward($url, 'member');
+ if (!$visibility->shouldHideItems) {
+ return true;
+ }
+ if ($forward) {
+ // only forward to group if user can see it
+ $group = get_entity($page_owner_guid);
+ $forward_url = $group ? $group->getURL() : '';
+
+ if (!elgg_is_logged_in()) {
+ $_SESSION['last_forward_from'] = current_page_url();
+ $forward_reason = 'login';
+ } else {
+ $forward_reason = 'member';
+ }
+
+ register_error(elgg_echo($visibility->reasonHidden));
+ forward($forward_url, $forward_reason);
}
- return $allowed;
+ return false;
}
/**
diff --git a/engine/lib/navigation.php b/engine/lib/navigation.php
index 8c3952594..86624cd7c 100644
--- a/engine/lib/navigation.php
+++ b/engine/lib/navigation.php
@@ -308,6 +308,32 @@ function elgg_site_menu_setup($hook, $type, $return, $params) {
$return['more'] = array_splice($return['default'], $max_display_items);
}
}
+
+ // check if we have anything selected
+ $selected = false;
+ foreach ($return as $section_name => $section) {
+ foreach ($section as $key => $item) {
+ if ($item->getSelected()) {
+ $selected = true;
+ break 2;
+ }
+ }
+ }
+
+ if (!$selected) {
+ // nothing selected, match name to context
+ foreach ($return as $section_name => $section) {
+ foreach ($section as $key => $item) {
+ // only highlight internal links
+ if (strpos($item->getHref(), elgg_get_site_url()) === 0) {
+ if ($item->getName() == elgg_get_context()) {
+ $return[$section_name][$key]->setSelected(true);
+ break 2;
+ }
+ }
+ }
+ }
+ }
return $return;
}
diff --git a/engine/lib/objects.php b/engine/lib/objects.php
index f186c66cb..e5e8f67c4 100644
--- a/engine/lib/objects.php
+++ b/engine/lib/objects.php
@@ -31,6 +31,7 @@ function get_object_entity_as_row($guid) {
* @param string $description The object's description
*
* @return bool
+ * @access private
*/
function create_object_entity($guid, $title, $description) {
global $CONFIG;
diff --git a/engine/lib/output.php b/engine/lib/output.php
index 352de863b..9295f2173 100644
--- a/engine/lib/output.php
+++ b/engine/lib/output.php
@@ -16,7 +16,7 @@
**/
function parse_urls($text) {
// @todo this causes problems with <attr = "val">
- // must be ing <attr="val"> format (no space).
+ // must be in <attr="val"> format (no space).
// By default htmlawed rewrites tags to this format.
// if PHP supported conditional negative lookbehinds we could use this:
// $r = preg_replace_callback('/(?<!=)(?<![ ])?(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i',
@@ -43,51 +43,26 @@ function parse_urls($text) {
/**
* Create paragraphs from text with line spacing
- * Borrowed from Wordpress.
*
* @param string $pee The string
- * @param bool $br Add BRs?
+ * @deprecated Use elgg_autop instead
+ * @todo Add deprecation warning in 1.9
*
- * @todo Rewrite
* @return string
**/
-function autop($pee, $br = 1) {
- $pee = $pee . "\n"; // just to make things a little easier, pad the end
- $pee = preg_replace('|<br />\s*<br />|', "\n\n", $pee);
- // Space things out a little
- $allblocks = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|map|area|blockquote|address|math|style|input|p|h[1-6]|hr)';
- $pee = preg_replace('!(<' . $allblocks . '[^>]*>)!', "\n$1", $pee);
- $pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee);
- $pee = str_replace(array("\r\n", "\r"), "\n", $pee); // cross-platform newlines
- if (strpos($pee, '<object') !== false) {
- $pee = preg_replace('|\s*<param([^>]*)>\s*|', "<param$1>", $pee); // no pee inside object/embed
- $pee = preg_replace('|\s*</embed>\s*|', '</embed>', $pee);
- }
- $pee = preg_replace("/\n\n+/", "\n\n", $pee); // take care of duplicates
- $pee = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "<p>$1</p>\n", $pee); // make paragraphs, including one at the end
- $pee = preg_replace('|<p>\s*?</p>|', '', $pee); // under certain strange conditions it could create a P of entirely whitespace
- $pee = preg_replace('!<p>([^<]+)\s*?(</(?:div|address|form)[^>]*>)!', "<p>$1</p>$2", $pee);
- $pee = preg_replace('|<p>|', "$1<p>", $pee);
- $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); // don't pee all over a tag
- $pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee); // problem with nested lists
- $pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee);
- $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee);
- $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee);
- $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee);
- if ($br) {
- $pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', create_function('$matches', 'return str_replace("\n", "<WPPreserveNewline />", $matches[0]);'), $pee);
- $pee = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $pee); // optionally make line breaks
- $pee = str_replace('<WPPreserveNewline />', "\n", $pee);
- }
- $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*<br />!', "$1", $pee);
- $pee = preg_replace('!<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee);
- //if (strpos($pee, '<pre') !== false) {
- // mind the space between the ? and >. Only there because of the comment.
- // $pee = preg_replace_callback('!(<pre.*? >)(.*?)</pre>!is', 'clean_pre', $pee );
- //}
- $pee = preg_replace("|\n</p>$|", '</p>', $pee);
-
- return $pee;
+function autop($pee) {
+ return elgg_autop($pee);
+}
+
+/**
+ * Create paragraphs from text with line spacing
+ *
+ * @param string $string The string
+ *
+ * @return string
+ **/
+function elgg_autop($string) {
+ return ElggAutoP::getInstance()->process($string);
}
/**
@@ -312,6 +287,8 @@ function elgg_get_friendly_title($title) {
// handle some special cases
$title = str_replace('&amp;', 'and', $title);
+ // quotes and angle brackets stored in the database as html encoded
+ $title = htmlspecialchars_decode($title);
$title = ElggTranslit::urlize($title);
@@ -384,7 +361,7 @@ function elgg_get_friendly_time($time) {
/**
* Strip tags and offer plugins the chance.
* Plugins register for output:strip_tags plugin hook.
- * Original string included in $params['original_string']
+ * Original string included in $params['original_string']
*
* @param string $string Formatted string
*
@@ -440,3 +417,32 @@ function _elgg_html_decode($string) {
);
return $string;
}
+
+/**
+ * Unit tests for Output
+ *
+ * @param sting $hook unit_test
+ * @param string $type system
+ * @param mixed $value Array of tests
+ * @param mixed $params Params
+ *
+ * @return array
+ * @access private
+ */
+function output_unit_test($hook, $type, $value, $params) {
+ global $CONFIG;
+ $value[] = $CONFIG->path . 'engine/tests/api/output.php';
+ return $value;
+}
+
+/**
+ * Initialise the Output subsystem.
+ *
+ * @return void
+ * @access private
+ */
+function output_init() {
+ elgg_register_plugin_hook_handler('unit_test', 'system', 'output_unit_test');
+}
+
+elgg_register_event_handler('init', 'system', 'output_init');
diff --git a/engine/lib/pageowner.php b/engine/lib/pageowner.php
index 0cf0e0625..94765feee 100644
--- a/engine/lib/pageowner.php
+++ b/engine/lib/pageowner.php
@@ -37,6 +37,8 @@ function elgg_get_page_owner_guid($guid = 0) {
/**
* Gets the owner entity for the current page.
*
+ * @note Access is disabled when getting the page owner entity.
+ *
* @return ElggEntity|false The current page owner or false if none.
*
* @since 1.8.0
@@ -44,10 +46,14 @@ function elgg_get_page_owner_guid($guid = 0) {
function elgg_get_page_owner_entity() {
$guid = elgg_get_page_owner_guid();
if ($guid > 0) {
- return get_entity($guid);
+ $ia = elgg_set_ignore_access(true);
+ $owner = get_entity($guid);
+ elgg_set_ignore_access($ia);
+
+ return $owner;
}
- return FALSE;
+ return false;
}
/**
@@ -75,6 +81,8 @@ function elgg_set_page_owner_guid($guid) {
* <handler>/edit/<entity guid>
* <handler>/group/<group guid>
*
+ * @note Access is disabled while finding the page owner for the group gatekeeper functions.
+ *
*
* @param string $hook 'page_owner'
* @param string $entity_type 'system'
@@ -90,6 +98,8 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)
return $returnvalue;
}
+ $ia = elgg_set_ignore_access(true);
+
$username = get_input("username");
if ($username) {
// @todo using a username of group:<guid> is deprecated
@@ -97,6 +107,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)
preg_match('/group\:([0-9]+)/i', $username, $matches);
$guid = $matches[1];
if ($entity = get_entity($guid)) {
+ elgg_set_ignore_access($ia);
return $entity->getGUID();
}
}
@@ -109,6 +120,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)
$owner = get_input("owner_guid");
if ($owner) {
if ($user = get_entity($owner)) {
+ elgg_set_ignore_access($ia);
return $user->getGUID();
}
}
@@ -130,6 +142,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)
case 'friends':
$user = get_user_by_username($segments[2]);
if ($user) {
+ elgg_set_ignore_access($ia);
return $user->getGUID();
}
break;
@@ -137,6 +150,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)
case 'edit':
$entity = get_entity($segments[2]);
if ($entity) {
+ elgg_set_ignore_access($ia);
return $entity->getContainerGUID();
}
break;
@@ -144,6 +158,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)
case 'group':
$entity = get_entity($segments[2]);
if ($entity) {
+ elgg_set_ignore_access($ia);
return $entity->getGUID();
}
break;
@@ -151,7 +166,7 @@ function default_page_owner_handler($hook, $entity_type, $returnvalue, $params)
}
}
- return $returnvalue;
+ elgg_set_ignore_access($ia);
}
/**
diff --git a/engine/lib/plugins.php b/engine/lib/plugins.php
index ca4a957f4..94aff277e 100644
--- a/engine/lib/plugins.php
+++ b/engine/lib/plugins.php
@@ -208,6 +208,7 @@ function elgg_get_plugin_from_id($plugin_id) {
'type' => 'object',
'subtype' => 'plugin',
'joins' => array("JOIN {$db_prefix}objects_entity oe on oe.guid = e.guid"),
+ 'selects' => array("oe.title", "oe.description"),
'wheres' => array("oe.title = '$plugin_id'"),
'limit' => 1
);
diff --git a/engine/lib/private_settings.php b/engine/lib/private_settings.php
index 1fa9bdb66..7541f7b3b 100644
--- a/engine/lib/private_settings.php
+++ b/engine/lib/private_settings.php
@@ -349,11 +349,6 @@ function set_private_setting($entity_guid, $name, $value) {
$name = sanitise_string($name);
$value = sanitise_string($value);
- $entity = get_entity($entity_guid);
- if (!$entity instanceof ElggEntity) {
- return false;
- }
-
$result = insert_data("INSERT into {$CONFIG->dbprefix}private_settings
(entity_guid, name, value) VALUES
($entity_guid, '$name', '$value')
diff --git a/engine/lib/sites.php b/engine/lib/sites.php
index 8b772668d..d9eb2d25e 100644
--- a/engine/lib/sites.php
+++ b/engine/lib/sites.php
@@ -58,6 +58,7 @@ function get_site_entity_as_row($guid) {
* @param string $url URL of the site
*
* @return bool
+ * @access private
*/
function create_site_entity($guid, $name, $description, $url) {
global $CONFIG;
diff --git a/engine/lib/upgrade.php b/engine/lib/upgrade.php
index f0874a483..f4f4b16f5 100644
--- a/engine/lib/upgrade.php
+++ b/engine/lib/upgrade.php
@@ -311,3 +311,58 @@ function elgg_upgrade_bootstrap_17_to_18() {
return elgg_set_processed_upgrades($processed_upgrades);
}
+
+/**
+ * Creates a table {prefix}upgrade_lock that is used as a mutex for upgrades.
+ *
+ * @see _elgg_upgrade_lock()
+ *
+ * @return bool
+ * @access private
+ */
+function _elgg_upgrade_lock() {
+ global $CONFIG;
+
+ if (!_elgg_upgrade_is_locked()) {
+ // lock it
+ insert_data("create table {$CONFIG->dbprefix}upgrade_lock (id INT)");
+ elgg_log('Locked for upgrade.', 'NOTICE');
+ return true;
+ }
+
+ elgg_log('Cannot lock for upgrade: already locked.', 'WARNING');
+ return false;
+}
+
+/**
+ * Unlocks upgrade.
+ *
+ * @see _elgg_upgrade_lock()
+ *
+ * @access private
+ */
+function _elgg_upgrade_unlock() {
+ global $CONFIG;
+ delete_data("drop table {$CONFIG->dbprefix}upgrade_lock");
+ elgg_log('Upgrade unlocked.', 'NOTICE');
+}
+
+/**
+ * Checks if upgrade is locked
+ *
+ * @return bool
+ * @access private
+ */
+function _elgg_upgrade_is_locked() {
+ global $CONFIG, $DB_QUERY_CACHE;
+
+ $is_locked = count(get_data("show tables like '{$CONFIG->dbprefix}upgrade_lock'"));
+
+ // Invalidate query cache
+ if ($DB_QUERY_CACHE) {
+ $DB_QUERY_CACHE->clear();
+ elgg_log("Query cache invalidated", 'NOTICE');
+ }
+
+ return $is_locked;
+}
diff --git a/engine/lib/upgrades/2011010101.php b/engine/lib/upgrades/2011010101.php
index a1ee92622..f4411ee20 100644
--- a/engine/lib/upgrades/2011010101.php
+++ b/engine/lib/upgrades/2011010101.php
@@ -93,4 +93,6 @@ $processed_upgrades[] = '2011010101.php';
$processed_upgrades = array_unique($processed_upgrades);
elgg_set_processed_upgrades($processed_upgrades);
+_elgg_upgrade_unlock();
+
forward('upgrade.php');
diff --git a/engine/lib/users.php b/engine/lib/users.php
index 527eff3cd..95ef9d176 100644
--- a/engine/lib/users.php
+++ b/engine/lib/users.php
@@ -44,6 +44,7 @@ function get_user_entity_as_row($guid) {
* @param string $code A code
*
* @return bool
+ * @access private
*/
function create_user_entity($guid, $name, $username, $password, $salt, $email, $language, $code) {
global $CONFIG;
diff --git a/engine/lib/views.php b/engine/lib/views.php
index e24ccb942..01291b889 100644
--- a/engine/lib/views.php
+++ b/engine/lib/views.php
@@ -1236,6 +1236,15 @@ function elgg_view_river_item($item, array $vars = array()) {
// subject is disabled or subject/object deleted
return '';
}
+ // Don't hide objects in closed groups that a user can see.
+ // see http://trac.elgg.org/ticket/4789
+// else {
+// // hide based on object's container
+// $visibility = ElggGroupItemVisibility::factory($object->container_guid);
+// if ($visibility->shouldHideItems) {
+// return '';
+// }
+// }
$vars['item'] = $item;
diff --git a/engine/lib/xml.php b/engine/lib/xml.php
index 813bc4ee0..ff82d7e8a 100644
--- a/engine/lib/xml.php
+++ b/engine/lib/xml.php
@@ -101,47 +101,11 @@ function serialise_array_to_xml(array $data, $n = 0) {
/**
* Parse an XML file into an object.
- * Based on code from http://de.php.net/manual/en/function.xml-parse-into-struct.php by
- * efredricksen at gmail dot com
*
* @param string $xml The XML
*
* @return object
*/
function xml_to_object($xml) {
- $parser = xml_parser_create();
-
- // Parse $xml into a structure
- xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
- xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
- xml_parse_into_struct($parser, $xml, $tags);
-
- xml_parser_free($parser);
-
- $elements = array();
- $stack = array();
-
- foreach ($tags as $tag) {
- $index = count($elements);
-
- if ($tag['type'] == "complete" || $tag['type'] == "open") {
- $elements[$index] = new XmlElement;
- $elements[$index]->name = $tag['tag'];
- $elements[$index]->attributes = elgg_extract('attributes', $tag, '');
- $elements[$index]->content = elgg_extract('value', $tag, '');
-
- if ($tag['type'] == "open") {
- $elements[$index]->children = array();
- $stack[count($stack)] = &$elements;
- $elements = &$elements[$index]->children;
- }
- }
-
- if ($tag['type'] == "close") {
- $elements = &$stack[count($stack) - 1];
- unset($stack[count($stack) - 1]);
- }
- }
-
- return $elements[0];
+ return new ElggXMLElement($xml);
}
diff --git a/engine/tests/api/access_collections.php b/engine/tests/api/access_collections.php
index bea995a6e..ebcd7d318 100644
--- a/engine/tests/api/access_collections.php
+++ b/engine/tests/api/access_collections.php
@@ -268,4 +268,26 @@ class ElggCoreAccessCollectionsTest extends ElggCoreUnitTest {
$group->delete();
}
+
+ public function testAccessCaching() {
+ // create a new user to check against
+ $user = new ElggUser();
+ $user->username = 'access_test_user';
+ $user->save();
+
+ foreach (array('get_access_list', 'get_access_array') as $func) {
+ $cache = _elgg_get_access_cache();
+ $cache->clear();
+
+ // admin users run tests, so disable access
+ elgg_set_ignore_access(true);
+ $access = $func($user->getGUID());
+
+ elgg_set_ignore_access(false);
+ $access2 = $func($user->getGUID());
+ $this->assertNotEqual($access, $access2, "Access test for $func");
+ }
+
+ $user->delete();
+ }
}
diff --git a/engine/tests/api/output.php b/engine/tests/api/output.php
new file mode 100644
index 000000000..c3d5aa8c6
--- /dev/null
+++ b/engine/tests/api/output.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Test case for ElggAutoP functionality.
+ */
+class ElggCoreOutputAutoPTest extends ElggCoreUnitTest {
+
+ /**
+ * @var ElggAutoP
+ */
+ protected $_autop;
+
+ public function setUp() {
+ $this->_autop = new ElggAutoP();
+ }
+
+ public function testDomRoundtrip() {
+ $d = dir(dirname(dirname(__FILE__)) . '/test_files/output/autop');
+ $in = file_get_contents($d->path . "/domdoc_in.html");
+ $exp = file_get_contents($d->path . "/domdoc_exp.html");
+ $exp = $this->flattenString($exp);
+
+ $doc = new DOMDocument();
+ libxml_use_internal_errors(true);
+ $doc->loadHTML("<html><meta http-equiv='content-type' content='text/html; charset=utf-8'><body>"
+ . $in . '</body></html>');
+ $serialized = $doc->saveHTML();
+ list(,$out) = explode('<body>', $serialized, 2);
+ list($out) = explode('</body>', $out, 2);
+ $out = $this->flattenString($out);
+
+ $this->assertEqual($exp, $out, "DOMDocument's parsing/serialization roundtrip");
+ }
+
+ public function testProcess() {
+ $data = $this->provider();
+ foreach ($data as $row) {
+ list($test, $in, $exp) = $row;
+ $exp = $this->flattenString($exp);
+ $out = $this->_autop->process($in);
+ $out = $this->flattenString($out);
+
+ $this->assertEqual($exp, $out, "Equality case {$test}");
+ }
+ }
+
+ public function provider() {
+ $d = dir(dirname(dirname(__FILE__)) . '/test_files/output/autop');
+ $tests = array();
+ while (false !== ($entry = $d->read())) {
+ if (preg_match('/^([a-z\\-]+)\.in\.html$/i', $entry, $m)) {
+ $tests[] = $m[1];
+ }
+ }
+
+ $data = array();
+ foreach ($tests as $test) {
+ $data[] = array(
+ $test,
+ file_get_contents($d->path . '/' . "{$test}.in.html"),
+ file_get_contents($d->path . '/' . "{$test}.exp.html"),
+ );
+ }
+ return $data;
+ }
+
+ /**
+ * Different versions of PHP return different whitespace between tags.
+ * Removing all line breaks normalizes that.
+ */
+ public function flattenString($string) {
+ $r = preg_replace('/[\n\r]+/', '', $string);
+ return $r;
+ }
+} \ No newline at end of file
diff --git a/engine/tests/test_files/output/autop/block-a.exp.norun.html b/engine/tests/test_files/output/autop/block-a.exp.norun.html
new file mode 100644
index 000000000..addf29dec
--- /dev/null
+++ b/engine/tests/test_files/output/autop/block-a.exp.norun.html
@@ -0,0 +1,6 @@
+
+<p>HTML5 allows A to contain block-level content</p>
+<a href="foo"><h3>A treated as block</h3>
+<p>Read more</p>
+</a>
+<p><a href="foo">A treated as<br /> inline</a></p>
diff --git a/engine/tests/test_files/output/autop/block-a.in.norun.html b/engine/tests/test_files/output/autop/block-a.in.norun.html
new file mode 100644
index 000000000..fc2dac43a
--- /dev/null
+++ b/engine/tests/test_files/output/autop/block-a.in.norun.html
@@ -0,0 +1,9 @@
+HTML5 allows A to contain block-level content
+<a href="foo">
+
+ <h3>A treated as block</h3>
+
+ Read more
+</a>
+<a href="foo">A treated as
+ inline</a>
diff --git a/engine/tests/test_files/output/autop/domdoc_exp.html b/engine/tests/test_files/output/autop/domdoc_exp.html
new file mode 100644
index 000000000..8480c1083
--- /dev/null
+++ b/engine/tests/test_files/output/autop/domdoc_exp.html
@@ -0,0 +1,46 @@
+›
+Vietnamese - Tiếng Việt
+
+<h1>h1</h1>
+<p>Paragraph <a href="http://google.com/">link</a> <strong>Bold</strong> <em>italic</em> <em><strong>bolditalic</strong></em> <span style="background-color: #ffff00; "></span></p>
+<h2>h2</h2>
+<p>Paragraph <span style="font-size: xx-small;">size1</span> <span style="font-size: x-small;">size2</span> <span style="font-size: medium;">size4</span></p>
+<h3>h3</h3>
+<p>Paragraph <span style="text-decoration: underline;">underline</span> <span style="text-decoration: line-through;">strikethrough</span> <span style="color: #ff0000;">color</span> <span style="background-color: #ffff00; ">background</span></p>
+<blockquote>
+ <p>Blockquoted paragraph</p>
+</blockquote>
+<p>Paragraph following blockquote</p>
+<ul><li>Unordered</li>
+ <li>List</li>
+</ul><p>Paragraph between lists</p>
+<ol><li>Ordered</li>
+ <li>List</li>
+</ol><p>Paragraph between lists</p>
+<ul><li>OL list</li>
+ <li>nested<ol><li>inside a</li>
+ <li>UL list</li>
+ </ol></li>
+</ul><p>Paragraph between lists</p>
+<table border="0"><tbody><tr><td>Table with</td>
+ <td></td>
+ </tr><tr><td></td>
+ <td>border=0</td>
+ </tr></tbody></table><p>Paragraph</p>
+<ol><li>UL list</li>
+ <li>nested
+ <ul><li>inside a</li>
+ <li>OL list</li>
+ </ul></li>
+</ol><p>Paragraph between tables</p>
+<table border="1" cellpadding="5"><tbody><tr><td>Table with border=1</td>
+ <td></td>
+ </tr><tr><td></td>
+ <td>cellpadding = 5</td>
+ </tr></tbody></table><p>Paragraph between tables</p>
+<table border="2"><tbody><tr><td>Table with</td>
+ <td></td>
+ </tr><tr><td></td>
+ <td>border=2</td>
+ </tr></tbody></table> \ No newline at end of file
diff --git a/engine/tests/test_files/output/autop/domdoc_in.html b/engine/tests/test_files/output/autop/domdoc_in.html
new file mode 100644
index 000000000..4c465b435
--- /dev/null
+++ b/engine/tests/test_files/output/autop/domdoc_in.html
@@ -0,0 +1,80 @@
+&#8250;
+&nbsp;
+Vietnamese - Tiếng Việt
+
+<h1>h1</h1>
+<p>Paragraph <a href="http://google.com/">link</a> <strong>Bold</strong> <em>italic</em> <em><strong>bolditalic</strong></em>&nbsp;<span style="background-color: #ffff00; "></span></p>
+<h2>h2</h2>
+<p>Paragraph <span style="font-size: xx-small;">size1</span> <span style="font-size: x-small;">size2</span> <span style="font-size: medium;">size4</span></p>
+<h3>h3</h3>
+<p>Paragraph <span style="text-decoration: underline;">underline</span> <span style="text-decoration: line-through;">strikethrough</span> <span style="color: #ff0000;">color</span> <span style="background-color: #ffff00; ">background</span></p>
+<blockquote>
+ <p>Blockquoted paragraph</p>
+</blockquote>
+<p>Paragraph following blockquote</p>
+<ul>
+ <li>Unordered</li>
+ <li>List</li>
+</ul>
+<p>Paragraph between lists</p>
+<ol>
+ <li>Ordered</li>
+ <li>List</li>
+</ol>
+<p>Paragraph between lists</p>
+<ul>
+ <li>OL list</li>
+ <li>nested<ol>
+ <li>inside a</li>
+ <li>UL list</li>
+ </ol></li>
+</ul>
+<p>Paragraph between lists</p>
+<table border="0">
+ <tbody>
+ <tr>
+ <td>Table with</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>border=0</td>
+ </tr>
+ </tbody>
+</table>
+<p>Paragraph</p>
+<ol>
+ <li>UL list</li>
+ <li>nested
+ <ul>
+ <li>inside a</li>
+ <li>OL list</li>
+ </ul>
+ </li>
+</ol>
+<p>Paragraph between tables</p>
+<table border="1" cellpadding="5">
+ <tbody>
+ <tr>
+ <td>Table with border=1</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>cellpadding = 5</td>
+ </tr>
+ </tbody>
+</table>
+<p>Paragraph between tables</p>
+<table border="2">
+ <tbody>
+ <tr>
+ <td>Table with</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>border=2</td>
+ </tr>
+ </tbody>
+</table> \ No newline at end of file
diff --git a/engine/tests/test_files/output/autop/typical-post.exp.html b/engine/tests/test_files/output/autop/typical-post.exp.html
new file mode 100644
index 000000000..f9d75a114
--- /dev/null
+++ b/engine/tests/test_files/output/autop/typical-post.exp.html
@@ -0,0 +1,84 @@
+<h2>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h2>
+<p><img class="alignright size-thumbnail wp-image-905" title="Surest Things mixing session in Adobe Audition" src="http://www.mrclay.org/wp-content/uploads/2010/09/surestThings_audition-150x150.png" alt="screenshot of Audition mixing session" width="150" height="150">Vivamus enim ante, <em>mattis eget imperdiet nec, pharetra vel velit.</em> Sed at euismod nibh. Praesent lacus tellus, <a href="http://google.com/">posuere et convallis</a> a, <strong>mollis et tellus. Suspendisse potenti</strong>. Phasellus tincidunt dignissim est eget mattis. Vestibulum lacinia <del>condimentum tellus, non vestibulum erat dapibus</del> quis. Aliquam arcu nibh, viverra adipiscing eleifend quis, pretium vitae ipsum.</p>
+
+<p>Curabitur turpis ante, <span style="color: #993300;">congue ac dapibus quis, vehicula ac orci.</span> Nunc luctus neque non massa porta sed pharetra ante accumsan. <a href="http://google.com/">Nam suscipit</a> risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor.</p>
+<h3>Donec at massa ante, sagittis fermentum urna.</h3><blockquote>
+<p>Mauris volutpat est id massa volutpat lacinia. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In in nisl mauris. In aliquet pretium nisl, vel convallis neque cursus vitae. Curabitur id mauris in urna gravida ornare.</p>
+
+<p>[caption id="attachment_719" align="alignleft" width="150" caption="Ibanez AGB140 Bass"]<img class="size-thumbnail wp-image-719" title="Ibanez AGB140 Bass" src="http://www.mrclay.org/wp-content/uploads/2010/04/agb140-e1271773766573-150x150.jpg" alt="Ibanez AGB140 Bass" width="150" height="150">[/caption]</p>
+
+<p>Aenean <a href="http://google.com/">aliquet cursus purus sed gravida. Cras auctor euismod justo, ac dictum purus facilisis dignissim.</a> Quisque facilisis porta sem, ac suscipit quam molestie nec. Pellentesque quis hendrerit enim. Vivamus tempor erat diam. Sed eu felis nunc. Cras posuere lorem commodo turpis mollis sagittis. Mauris lobortis nunc felis.</p>
+
+<p>Maecenas elit lorem, varius sed condimentum ac, cursus et magna. Nam ut massa id augue consectetur porttitor eleifend in nunc. Curabitur cursus varius dictum. Vestibulum vel justo et neque tempus placerat a vel sapien.</p>
+</blockquote>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus enim ante, mattis eget imperdiet nec, <a href="http://google.com/">pharetra </a>vel velit. Sed at euismod nibh. Praesent lacus tellus, posuere et convallis a, mollis et tellus.</p>
+<pre><code>&lt;?php
+class DataTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider provider
+ */
+ public function testAdd($a, $b, $c)
+ {
+ $this-&gt;assertEquals($c, $a + $b);
+ }
+
+ public function provider()
+ {
+ return array(
+ array(0, 0, 0),
+ array(0, 1, 1),
+ array(1, 0, 1),
+ array(1, 1, 3)
+ );
+ }
+}</code></pre><ul><li>Suspendisse potenti. Phasellus tincidunt dignissim est eget mattis.</li>
+ <li>Vestibulum lacinia condimentum tellus, non vestibulum erat dapibus quis.</li>
+ <li>Aliquam arcu nibh, <a href="http://google.com/">viverra</a> adipiscing eleifend quis, pretium vitae ipsum.</li>
+ <li>Curabitur turpis ante, congue ac <a href="http://google.com/">dapibus quis</a>, vehicula ac orci.</li>
+</ul>
+<p>Nunc luctus neque non massa porta sed pharetra ante accumsan. Nam suscipit risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor. Donec at massa ante, sagittis fermentum urna.</p>
+
+<p><object width="480" height="390"><param name="movie" value="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&amp;hl=en_US"><param name="allowFullScreen" value="true"><param name="allowscriptaccess" value="always"><embed type="application/x-shockwave-flash" width="480" height="390" src="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&amp;hl=en_US" allowfullscreen="true" allowscriptaccess="always"></embed></object></p>
+<h2>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h2>
+<p><img class="alignright size-thumbnail wp-image-905" title="Surest Things mixing session in Adobe Audition" src="http://www.mrclay.org/wp-content/uploads/2010/09/surestThings_audition-150x150.png" alt="screenshot of Audition mixing session" width="150" height="150">Vivamus enim ante, <em>mattis eget imperdiet nec, pharetra vel velit.</em> Sed at euismod nibh. Praesent lacus tellus, <a href="http://google.com/">posuere et convallis</a> a, <strong>mollis et tellus. Suspendisse potenti</strong>. Phasellus tincidunt dignissim est eget mattis. Vestibulum lacinia <del>condimentum tellus, non vestibulum erat dapibus</del> quis. Aliquam arcu nibh, viverra adipiscing eleifend quis, pretium vitae ipsum.</p>
+
+<p>Curabitur turpis ante, <span style="color: #993300;">congue ac dapibus quis, vehicula ac orci.</span> Nunc luctus neque non massa porta sed pharetra ante accumsan. <a href="http://google.com/">Nam suscipit</a> risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor.</p>
+<h3>Donec at massa ante, sagittis fermentum urna.</h3><blockquote>
+<p>Mauris volutpat est id massa volutpat lacinia. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In in nisl mauris. In aliquet pretium nisl, vel convallis neque cursus vitae. Curabitur id mauris in urna gravida ornare.</p>
+
+<p>[caption id="attachment_719" align="alignleft" width="150" caption="Ibanez AGB140 Bass"]<img class="size-thumbnail wp-image-719" title="Ibanez AGB140 Bass" src="http://www.mrclay.org/wp-content/uploads/2010/04/agb140-e1271773766573-150x150.jpg" alt="Ibanez AGB140 Bass" width="150" height="150">[/caption]</p>
+
+<p>Aenean <a href="http://google.com/">aliquet cursus purus sed gravida. Cras auctor euismod justo, ac dictum purus facilisis dignissim.</a> Quisque facilisis porta sem, ac suscipit quam molestie nec. Pellentesque quis hendrerit enim. Vivamus tempor erat diam. Sed eu felis nunc. Cras posuere lorem commodo turpis mollis sagittis. Mauris lobortis nunc felis.</p>
+
+<p>Maecenas elit lorem, varius sed condimentum ac, cursus et magna. Nam ut massa id augue consectetur porttitor eleifend in nunc. Curabitur cursus varius dictum. Vestibulum vel justo et neque tempus placerat a vel sapien.</p>
+</blockquote>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus enim ante, mattis eget imperdiet nec, <a href="http://google.com/">pharetra </a>vel velit. Sed at euismod nibh. Praesent lacus tellus, posuere et convallis a, mollis et tellus.</p>
+<pre><code>&lt;?php
+class DataTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider provider
+ */
+ public function testAdd($a, $b, $c)
+ {
+ $this-&gt;assertEquals($c, $a + $b);
+ }
+
+ public function provider()
+ {
+ return array(
+ array(0, 0, 0),
+ array(0, 1, 1),
+ array(1, 0, 1),
+ array(1, 1, 3)
+ );
+ }
+}</code></pre><ul><li>Suspendisse potenti. Phasellus tincidunt dignissim est eget mattis.</li>
+ <li>Vestibulum lacinia condimentum tellus, non vestibulum erat dapibus quis.</li>
+ <li>Aliquam arcu nibh, <a href="http://google.com/">viverra</a> adipiscing eleifend quis, pretium vitae ipsum.</li>
+ <li>Curabitur turpis ante, congue ac <a href="http://google.com/">dapibus quis</a>, vehicula ac orci.</li>
+</ul>
+<p>Nunc luctus neque non massa porta sed pharetra ante accumsan. Nam suscipit risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor. Donec at massa ante, sagittis fermentum urna.</p>
+
+<p><object width="480" height="390"><param name="movie" value="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&amp;hl=en_US"><param name="allowFullScreen" value="true"><param name="allowscriptaccess" value="always"><embed type="application/x-shockwave-flash" width="480" height="390" src="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&amp;hl=en_US" allowfullscreen="true" allowscriptaccess="always"></embed></object></p>
diff --git a/engine/tests/test_files/output/autop/typical-post.in.html b/engine/tests/test_files/output/autop/typical-post.in.html
new file mode 100644
index 000000000..6e4984cc4
--- /dev/null
+++ b/engine/tests/test_files/output/autop/typical-post.in.html
@@ -0,0 +1,89 @@
+<h2>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h2>
+<img class="alignright size-thumbnail wp-image-905" title="Surest Things mixing session in Adobe Audition" src="http://www.mrclay.org/wp-content/uploads/2010/09/surestThings_audition-150x150.png" alt="screenshot of Audition mixing session" width="150" height="150" />Vivamus enim ante, <em>mattis eget imperdiet nec, pharetra vel velit.</em> Sed at euismod nibh. Praesent lacus tellus, <a href="http://google.com/">posuere et convallis</a> a, <strong>mollis et tellus. Suspendisse potenti</strong>. Phasellus tincidunt dignissim est eget mattis. Vestibulum lacinia <del>condimentum tellus, non vestibulum erat dapibus</del> quis. Aliquam arcu nibh, viverra adipiscing eleifend quis, pretium vitae ipsum.
+
+Curabitur turpis ante, <span style="color: #993300;">congue ac dapibus quis, vehicula ac orci.</span> Nunc luctus neque non massa porta sed pharetra ante accumsan. <a href="http://google.com/">Nam suscipit</a> risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor.
+<h3>Donec at massa ante, sagittis fermentum urna.</h3>
+<blockquote>Mauris volutpat est id massa volutpat lacinia. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In in nisl mauris. In aliquet pretium nisl, vel convallis neque cursus vitae. Curabitur id mauris in urna gravida ornare.
+
+[caption id="attachment_719" align="alignleft" width="150" caption="Ibanez AGB140 Bass"]<img class="size-thumbnail wp-image-719" title="Ibanez AGB140 Bass" src="http://www.mrclay.org/wp-content/uploads/2010/04/agb140-e1271773766573-150x150.jpg" alt="Ibanez AGB140 Bass" width="150" height="150" />[/caption]
+
+Aenean <a href="http://google.com/">aliquet cursus purus sed gravida. Cras auctor euismod justo, ac dictum purus facilisis dignissim.</a> Quisque facilisis porta sem, ac suscipit quam molestie nec. Pellentesque quis hendrerit enim. Vivamus tempor erat diam. Sed eu felis nunc. Cras posuere lorem commodo turpis mollis sagittis. Mauris lobortis nunc felis.
+
+Maecenas elit lorem, varius sed condimentum ac, cursus et magna. Nam ut massa id augue consectetur porttitor eleifend in nunc. Curabitur cursus varius dictum. Vestibulum vel justo et neque tempus placerat a vel sapien.</blockquote>
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus enim ante, mattis eget imperdiet nec, <a href="http://google.com/">pharetra </a>vel velit. Sed at euismod nibh. Praesent lacus tellus, posuere et convallis a, mollis et tellus.
+
+<pre><code>&lt;?php
+class DataTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider provider
+ */
+ public function testAdd($a, $b, $c)
+ {
+ $this-&gt;assertEquals($c, $a + $b);
+ }
+
+ public function provider()
+ {
+ return array(
+ array(0, 0, 0),
+ array(0, 1, 1),
+ array(1, 0, 1),
+ array(1, 1, 3)
+ );
+ }
+}</code></pre>
+<ul>
+ <li>Suspendisse potenti. Phasellus tincidunt dignissim est eget mattis.</li>
+ <li>Vestibulum lacinia condimentum tellus, non vestibulum erat dapibus quis.</li>
+ <li>Aliquam arcu nibh, <a href="http://google.com/">viverra</a> adipiscing eleifend quis, pretium vitae ipsum.</li>
+ <li>Curabitur turpis ante, congue ac <a href="http://google.com/">dapibus quis</a>, vehicula ac orci.</li>
+</ul>
+Nunc luctus neque non massa porta sed pharetra ante accumsan. Nam suscipit risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor. Donec at massa ante, sagittis fermentum urna.
+
+<object width="480" height="390"><param name="movie" value="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&amp;hl=en_US" /><param name="allowFullScreen" value="true" /><param name="allowscriptaccess" value="always" /><embed type="application/x-shockwave-flash" width="480" height="390" src="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&amp;hl=en_US" allowfullscreen="true" allowscriptaccess="always"></embed></object>
+
+<h2>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h2>
+<img class="alignright size-thumbnail wp-image-905" title="Surest Things mixing session in Adobe Audition" src="http://www.mrclay.org/wp-content/uploads/2010/09/surestThings_audition-150x150.png" alt="screenshot of Audition mixing session" width="150" height="150" />Vivamus enim ante, <em>mattis eget imperdiet nec, pharetra vel velit.</em> Sed at euismod nibh. Praesent lacus tellus, <a href="http://google.com/">posuere et convallis</a> a, <strong>mollis et tellus. Suspendisse potenti</strong>. Phasellus tincidunt dignissim est eget mattis. Vestibulum lacinia <del>condimentum tellus, non vestibulum erat dapibus</del> quis. Aliquam arcu nibh, viverra adipiscing eleifend quis, pretium vitae ipsum.
+
+Curabitur turpis ante, <span style="color: #993300;">congue ac dapibus quis, vehicula ac orci.</span> Nunc luctus neque non massa porta sed pharetra ante accumsan. <a href="http://google.com/">Nam suscipit</a> risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor.
+<h3>Donec at massa ante, sagittis fermentum urna.</h3>
+<blockquote>Mauris volutpat est id massa volutpat lacinia. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In in nisl mauris. In aliquet pretium nisl, vel convallis neque cursus vitae. Curabitur id mauris in urna gravida ornare.
+
+[caption id="attachment_719" align="alignleft" width="150" caption="Ibanez AGB140 Bass"]<img class="size-thumbnail wp-image-719" title="Ibanez AGB140 Bass" src="http://www.mrclay.org/wp-content/uploads/2010/04/agb140-e1271773766573-150x150.jpg" alt="Ibanez AGB140 Bass" width="150" height="150" />[/caption]
+
+Aenean <a href="http://google.com/">aliquet cursus purus sed gravida. Cras auctor euismod justo, ac dictum purus facilisis dignissim.</a> Quisque facilisis porta sem, ac suscipit quam molestie nec. Pellentesque quis hendrerit enim. Vivamus tempor erat diam. Sed eu felis nunc. Cras posuere lorem commodo turpis mollis sagittis. Mauris lobortis nunc felis.
+
+Maecenas elit lorem, varius sed condimentum ac, cursus et magna. Nam ut massa id augue consectetur porttitor eleifend in nunc. Curabitur cursus varius dictum. Vestibulum vel justo et neque tempus placerat a vel sapien.</blockquote>
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus enim ante, mattis eget imperdiet nec, <a href="http://google.com/">pharetra </a>vel velit. Sed at euismod nibh. Praesent lacus tellus, posuere et convallis a, mollis et tellus.
+
+<pre><code>&lt;?php
+class DataTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider provider
+ */
+ public function testAdd($a, $b, $c)
+ {
+ $this-&gt;assertEquals($c, $a + $b);
+ }
+
+ public function provider()
+ {
+ return array(
+ array(0, 0, 0),
+ array(0, 1, 1),
+ array(1, 0, 1),
+ array(1, 1, 3)
+ );
+ }
+}</code></pre>
+<ul>
+ <li>Suspendisse potenti. Phasellus tincidunt dignissim est eget mattis.</li>
+ <li>Vestibulum lacinia condimentum tellus, non vestibulum erat dapibus quis.</li>
+ <li>Aliquam arcu nibh, <a href="http://google.com/">viverra</a> adipiscing eleifend quis, pretium vitae ipsum.</li>
+ <li>Curabitur turpis ante, congue ac <a href="http://google.com/">dapibus quis</a>, vehicula ac orci.</li>
+</ul>
+Nunc luctus neque non massa porta sed pharetra ante accumsan. Nam suscipit risus quis libero convallis viverra. Ut at arcu enim, vel pharetra dolor. Donec at massa ante, sagittis fermentum urna.
+
+<object width="480" height="390"><param name="movie" value="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&amp;hl=en_US" /><param name="allowFullScreen" value="true" /><param name="allowscriptaccess" value="always" /><embed type="application/x-shockwave-flash" width="480" height="390" src="http://www.youtube.com/v/zW9YOMaVTFI?fs=1&amp;hl=en_US" allowfullscreen="true" allowscriptaccess="always"></embed></object> \ No newline at end of file
diff --git a/engine/tests/test_files/output/autop/wp-welcome.exp.html b/engine/tests/test_files/output/autop/wp-welcome.exp.html
new file mode 100644
index 000000000..2f612e3dd
--- /dev/null
+++ b/engine/tests/test_files/output/autop/wp-welcome.exp.html
@@ -0,0 +1,22 @@
+
+<p>Welcome to WordPress! This post contains important information. After you read it, you can make it private to hide it from visitors but still have the information handy for future reference.</p>
+
+<p>First things first:</p>
+<ul><li><a href="%1%24s" title="Subscribe to the WordPress mailing list for Release Notifications">Subscribe to the WordPress mailing list for release notifications</a></li>
+</ul>
+<p>As a subscriber, you will receive an email every time an update is available (and only then). This will make it easier to keep your site up to date, and secure from evildoers.<br />When a new version is released, <a href="%2%24s" title="If you are already logged in, this will take you directly to the Dashboard">log in to the Dashboard</a> and follow the instructions.<br />Upgrading is a couple of clicks!</p>
+
+<p>Then you can start enjoying the WordPress experience:</p>
+<ul><li>Edit your personal information at <a href="%3%24s" title="Edit settings like your password, your display name and your contact information">Users &#8250; Your Profile</a></li>
+ <li>Start publishing at <a href="%4%24s" title="Create a new post">Posts &#8250; Add New</a> and at <a href="%5%24s" title="Create a new page">Pages &#8250; Add New</a></li>
+ <li>Browse and install plugins at <a href="%6%24s" title="Browse and install plugins at the official WordPress repository directly from your Dashboard">Plugins &#8250; Add New</a></li>
+ <li>Browse and install themes at <a href="%7%24s" title="Browse and install themes at the official WordPress repository directly from your Dashboard">Appearance &#8250; Add New Themes</a></li>
+ <li>Modify and prettify your website&#8217;s links at <a href="%8%24s" title="For example, select a link structure like: http://example.com/1999/12/post-name">Settings &#8250; Permalinks</a></li>
+ <li>Import content from another system or WordPress site at <a href="%9%24s" title="WordPress comes with importers for the most common publishing systems">Tools &#8250; Import</a></li>
+ <li>Find answers to your questions at the <a href="%10%24s" title="The official WordPress documentation, maintained by the WordPress community">WordPress Codex</a></li>
+</ul>
+<p>To keep this post for reference, <a href="%11%24s" title="Click to edit the content and settings of this post">click to edit it</a>, go to the Publish box and change its Visibility from Public to Private.</p>
+
+<p>Thank you for selecting WordPress. We wish you happy publishing!</p>
+
+<p>PS. Not yet subscribed for update notifications? <a href="%1%24s" title="Subscribe to the WordPress mailing list for Release Notifications">Do it now!</a></p>
diff --git a/engine/tests/test_files/output/autop/wp-welcome.in.html b/engine/tests/test_files/output/autop/wp-welcome.in.html
new file mode 100644
index 000000000..338ede73f
--- /dev/null
+++ b/engine/tests/test_files/output/autop/wp-welcome.in.html
@@ -0,0 +1,25 @@
+Welcome to WordPress! This post contains important information. After you read it, you can make it private to hide it from visitors but still have the information handy for future reference.
+
+First things first:
+<ul>
+ <li><a href="%1$s" title="Subscribe to the WordPress mailing list for Release Notifications">Subscribe to the WordPress mailing list for release notifications</a></li>
+</ul>
+As a subscriber, you will receive an email every time an update is available (and only then). This will make it easier to keep your site up to date, and secure from evildoers.
+When a new version is released, <a href="%2$s" title="If you are already logged in, this will take you directly to the Dashboard">log in to the Dashboard</a> and follow the instructions.
+Upgrading is a couple of clicks!
+
+Then you can start enjoying the WordPress experience:
+<ul>
+ <li>Edit your personal information at <a href="%3$s" title="Edit settings like your password, your display name and your contact information">Users &#8250; Your Profile</a></li>
+ <li>Start publishing at <a href="%4$s" title="Create a new post">Posts &#8250; Add New</a> and at <a href="%5$s" title="Create a new page">Pages &#8250; Add New</a></li>
+ <li>Browse and install plugins at <a href="%6$s" title="Browse and install plugins at the official WordPress repository directly from your Dashboard">Plugins &#8250; Add New</a></li>
+ <li>Browse and install themes at <a href="%7$s" title="Browse and install themes at the official WordPress repository directly from your Dashboard">Appearance &#8250; Add New Themes</a></li>
+ <li>Modify and prettify your website&#8217;s links at <a href="%8$s" title="For example, select a link structure like: http://example.com/1999/12/post-name">Settings &#8250; Permalinks</a></li>
+ <li>Import content from another system or WordPress site at <a href="%9$s" title="WordPress comes with importers for the most common publishing systems">Tools &#8250; Import</a></li>
+ <li>Find answers to your questions at the <a href="%10$s" title="The official WordPress documentation, maintained by the WordPress community">WordPress Codex</a></li>
+</ul>
+To keep this post for reference, <a href="%11$s" title="Click to edit the content and settings of this post">click to edit it</a>, go to the Publish box and change its Visibility from Public to Private.
+
+Thank you for selecting WordPress. We wish you happy publishing!
+
+PS. Not yet subscribed for update notifications? <a href="%1$s" title="Subscribe to the WordPress mailing list for Release Notifications">Do it now!</a>
diff --git a/engine/tests/test_files/output/autop/wpautop-fails.exp.html b/engine/tests/test_files/output/autop/wpautop-fails.exp.html
new file mode 100644
index 000000000..d018db4ff
--- /dev/null
+++ b/engine/tests/test_files/output/autop/wpautop-fails.exp.html
@@ -0,0 +1,31 @@
+
+<p>paragraph</p>
+
+<p>paragraph</p>
+<div class="whatever"><blockquote>
+<p>paragraph</p>
+</blockquote>
+<p>line</p>
+</div>
+<p>paragraph</p>
+<ul><li>line</li>
+<li>paragraph
+
+paragraph</li>
+</ul>
+<p>paragraph<br />line<br />line</p>
+<pre>Honor
+this whitespace
+</pre>
+<p>paragraph</p>
+<style><!--
+Do not alter!
+--></style>
+<p>paragraph <!-- do not alter --></p>
+<dl><dt>term</dt> <dd>paragraph
+
+<a href="xx"> <img src="yy"></a>
+
+paragraph</dd> </dl><div><a href="xx"> <img src="yy"></a></div>
+<p>Hello <a href="link"><br /><br />World</a></p>
+<p id="abc">Paragraph</p><div>Line</div> \ No newline at end of file
diff --git a/engine/tests/test_files/output/autop/wpautop-fails.in.html b/engine/tests/test_files/output/autop/wpautop-fails.in.html
new file mode 100644
index 000000000..9aa24be59
--- /dev/null
+++ b/engine/tests/test_files/output/autop/wpautop-fails.in.html
@@ -0,0 +1,41 @@
+
+paragraph
+
+paragraph <div class="whatever"><blockquote>
+ paragraph
+ </blockquote>
+ line
+</div>
+
+paragraph
+<ul>
+<li>line</li>
+<li>paragraph
+
+paragraph</li>
+</ul>
+paragraph
+line<br>
+ line
+<pre>Honor
+this whitespace
+</pre>
+paragraph
+<style><!--
+Do not alter!
+--></style>
+paragraph <!-- do not alter -->
+<dl> <dt>term</dt> <dd>paragraph
+
+<a href="xx"> <img src="yy" /> </a>
+
+paragraph</dd> </dl>
+<div><a href="xx"> <img src="yy" /> </a></div>
+
+Hello <a href="link">
+
+World</a>
+
+<p id="abc">Paragraph</p>
+
+<div>Line</div> \ No newline at end of file
diff --git a/engine/tests/test_files/output/autop/wysiwyg-test.exp.html b/engine/tests/test_files/output/autop/wysiwyg-test.exp.html
new file mode 100644
index 000000000..1f23d6154
--- /dev/null
+++ b/engine/tests/test_files/output/autop/wysiwyg-test.exp.html
@@ -0,0 +1,51 @@
+
+<p>&nbps;<br />&#8820;</p>
+<h1>h1</h1>
+<p>Paragraph <a href="http://google.com/">link</a> <strong>Bold</strong> <em>italic</em> <em><strong>bolditalic</strong></em>&nbsp;<span style="background-color: #ffff00; "></span></p>
+<h2>h2</h2>
+<p>Paragraph <span style="font-size: xx-small;">size1</span> <span style="font-size: x-small;">size2</span> <span style="font-size: medium;">size4</span></p>
+<h3>h3</h3>
+<p>Paragraph <span style="text-decoration: underline;">underline</span> <span style="text-decoration: line-through;">strikethrough</span> <span style="color: #ff0000;">color</span> <span style="background-color: #ffff00; ">background</span></p>
+<blockquote>
+<p>Blockquoted paragraph</p>
+</blockquote>
+<p>Paragraph following blockquote</p>
+<ul><li>Unordered</li>
+ <li>List</li>
+</ul>
+<p>Paragraph between lists</p>
+<ol><li>Ordered</li>
+ <li>List</li>
+</ol>
+<p>Paragraph between lists</p>
+<ul><li>OL list</li>
+ <li>nested
+ <ol><li>inside a</li>
+ <li>UL list</li>
+ </ol></li>
+</ul>
+<p>Paragraph between lists</p>
+<table border="0"><tbody><tr></tr><tr><td>Table with</td>
+ <td></td>
+ </tr><tr><td></td>
+ <td>border=0</td>
+ </tr></tbody></table>
+<p>Paragraph</p>
+<ol><li>UL list</li>
+ <li>nested
+ <ul><li>inside a</li>
+ <li>OL list</li>
+ </ul></li>
+</ol>
+<p>Paragraph between tables</p>
+<table border="1" cellpadding="5"><tbody><tr><td>Table with border=1</td>
+ <td></td>
+ </tr><tr><td></td>
+ <td>cellpadding = 5</td>
+ </tr></tbody></table>
+<p>Paragraph between tables</p>
+<table border="2"><tbody><tr><td>Table with</td>
+ <td></td>
+ </tr><tr><td></td>
+ <td>border=2</td>
+ </tr></tbody></table> \ No newline at end of file
diff --git a/engine/tests/test_files/output/autop/wysiwyg-test.in.html b/engine/tests/test_files/output/autop/wysiwyg-test.in.html
new file mode 100644
index 000000000..733b0e2ec
--- /dev/null
+++ b/engine/tests/test_files/output/autop/wysiwyg-test.in.html
@@ -0,0 +1,79 @@
+&nbps;
+&#8820;
+<h1>h1</h1>
+Paragraph <a href="http://google.com/">link</a> <strong>Bold</strong> <em>italic</em> <em><strong>bolditalic</strong></em>&nbsp;<span style="background-color: #ffff00; "></span>
+<h2>h2</h2>
+Paragraph <span style="font-size: xx-small;">size1</span> <span style="font-size: x-small;">size2</span> <span style="font-size: medium;">size4</span>
+<h3>h3</h3>
+Paragraph <span style="text-decoration: underline;">underline</span> <span style="text-decoration: line-through;">strikethrough</span> <span style="color: #ff0000;">color</span> <span style="background-color: #ffff00; ">background</span>
+<blockquote>Blockquoted paragraph</blockquote>
+Paragraph following blockquote
+<ul>
+ <li>Unordered</li>
+ <li>List</li>
+</ul>
+Paragraph between lists
+<ol>
+ <li>Ordered</li>
+ <li>List</li>
+</ol>
+Paragraph between lists
+<ul>
+ <li>OL list</li>
+ <li>nested
+ <ol>
+ <li>inside a</li>
+ <li>UL list</li>
+ </ol></li>
+</ul>
+Paragraph between lists
+<table border="0">
+ <tbody>
+ <tr>
+ </tr>
+ <tr>
+ <td>Table with</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>border=0</td>
+ </tr>
+ </tbody>
+</table>
+Paragraph
+<ol>
+ <li>UL list</li>
+ <li>nested
+ <ul>
+ <li>inside a</li>
+ <li>OL list</li>
+ </ul>
+ </li>
+</ol>
+Paragraph between tables
+<table border="1" cellpadding="5">
+ <tbody>
+ <tr>
+ <td>Table with border=1</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>cellpadding = 5</td>
+ </tr>
+ </tbody>
+</table>
+Paragraph between tables
+<table border="2">
+ <tbody>
+ <tr>
+ <td>Table with</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td>border=2</td>
+ </tr>
+ </tbody>
+</table> \ No newline at end of file