$reply->notice_id = $this->id;
$reply->profile_id = $profile->id;
+ $reply->modified = $this->created;
common_log(LOG_INFO, __METHOD__ . ": saving reply: notice $this->id to profile $profile->id");
$reply->notice_id = $this->id;
$reply->profile_id = $mentioned->id;
+ $reply->modified = $this->created;
$id = $reply->insert();
[reply]
notice_id = 129
profile_id = 129
-modified = 384
+modified = 142
+;modified = 384 ; skipping the mysql_timestamp mode so we can override its setting
replied_id = 1
[reply__keys]
// functions
$url = htmlspecialchars_decode($url);
- if(strpos($url, '@') !== false && strpos($url, ':') === false) {
- //url is an email address without the mailto: protocol
- $canon = "mailto:$url";
- $longurl = "mailto:$url";
- }else{
+ if (strpos($url, '@') !== false && strpos($url, ':') === false && Validate::email($url)) {
+ //url is an email address without the mailto: protocol
+ $canon = "mailto:$url";
+ $longurl = "mailto:$url";
+ } else {
$canon = File_redirection::_canonUrl($url);
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Extra profile bio-like fields
+ *
+ * @package ExtendedProfilePlugin
+ * @maintainer Brion Vibber <brion@status.net>
+ */
+class ExtendedProfilePlugin extends Plugin
+{
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'ExtendedProfile',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Brion Vibber',
+ 'homepage' => 'http://status.net/wiki/Plugin:ExtendedProfile',
+ 'rawdescription' =>
+ _m('UI extensions for additional profile fields.'));
+
+ return true;
+ }
+
+ /**
+ * Autoloader
+ *
+ * Loads our classes if they're requested.
+ *
+ * @param string $cls Class requested
+ *
+ * @return boolean hook return
+ */
+ function onAutoload($cls)
+ {
+ $lower = strtolower($cls);
+ switch ($lower)
+ {
+ case 'extendedprofile':
+ case 'extendedprofilewidget':
+ case 'profiledetailaction':
+ case 'profiledetailsettingsaction':
+ require_once dirname(__FILE__) . '/' . $lower . '.php';
+ return false;
+ case 'profile_detail':
+ require_once dirname(__FILE__) . '/' . ucfirst($lower) . '.php';
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Add paths to the router table
+ *
+ * Hook for RouterInitialized event.
+ *
+ * @param Net_URL_Mapper $m URL mapper
+ *
+ * @return boolean hook return
+ */
+ function onStartInitializeRouter($m)
+ {
+ $m->connect(':nickname/detail',
+ array('action' => 'profiledetail'),
+ array('nickname' => Nickname::DISPLAY_FMT));
+ $m->connect('settings/profile/detail',
+ array('action' => 'profiledetailsettings'));
+
+ return true;
+ }
+
+ function onCheckSchema()
+ {
+ $schema = Schema::get();
+ $schema->ensureTable('profile_detail', Profile_detail::schemaDef());
+
+ // @hack until key definition support is merged
+ Profile_detail::fixIndexes($schema);
+ return true;
+ }
+
+ function onEndAccountSettingsProfileMenuItem($widget, $menu)
+ {
+ // TRANS: Link title attribute in user account settings menu.
+ $title = _('Change additional profile settings');
+ // TRANS: Link description in user account settings menu.
+ $widget->showMenuItem('profiledetailsettings',_m('Details'),$title);
+ return true;
+ }
+
+ function onEndProfilePageProfileElements(HTMLOutputter $out, Profile $profile) {
+ $user = User::staticGet('id', $profile->id);
+ if ($user) {
+ $url = common_local_url('profiledetail', array('nickname' => $user->nickname));
+ $out->element('a', array('href' => $url), _m('More details...'));
+ }
+ return;
+ }
+
+}
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class Profile_detail extends Memcached_DataObject
+{
+ public $__table = 'submirror';
+
+ public $id;
+
+ public $profile_id;
+ public $field;
+ public $field_index; // relative ordering of multiple values in the same field
+
+ public $value; // primary text value
+ public $rel; // detail for some field types; eg "home", "mobile", "work" for phones or "aim", "irc", "xmpp" for IM
+ public $ref_profile; // for people types, allows pointing to a known profile in the system
+
+ public $created;
+ public $modified;
+
+ public /*static*/ function staticGet($k, $v=null)
+ {
+ return parent::staticGet(__CLASS__, $k, $v);
+ }
+
+ /**
+ * return table definition for DB_DataObject
+ *
+ * DB_DataObject needs to know something about the table to manipulate
+ * instances. This method provides all the DB_DataObject needs to know.
+ *
+ * @return array array of column definitions
+ */
+
+ function table()
+ {
+ return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+
+ 'profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'field' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'field_index' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+
+ 'value' => DB_DATAOBJECT_STR,
+ 'rel' => DB_DATAOBJECT_STR,
+ 'ref_profile' => DB_DATAOBJECT_ID,
+
+ 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
+ 'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
+ }
+
+ static function schemaDef()
+ {
+ // @fixme need a reverse key on (subscribed, subscriber) as well
+ return array(new ColumnDef('id', 'integer',
+ null, false, 'PRI'),
+
+ // @fixme need a unique index on these three
+ new ColumnDef('profile_id', 'integer',
+ null, false),
+ new ColumnDef('field', 'varchar',
+ 16, false),
+ new ColumnDef('field_index', 'integer',
+ null, false),
+
+ new ColumnDef('value', 'text',
+ null, true),
+ new ColumnDef('rel', 'varchar',
+ 16, true),
+ new ColumnDef('ref_profile', 'integer',
+ null, true),
+
+ new ColumnDef('created', 'datetime',
+ null, false),
+ new ColumnDef('modified', 'datetime',
+ null, false));
+ }
+
+ /**
+ * Temporary hack to set up the compound index, since we can't do
+ * it yet through regular Schema interface. (Coming for 1.0...)
+ *
+ * @param Schema $schema
+ * @return void
+ */
+ static function fixIndexes($schema)
+ {
+ try {
+ // @fixme this won't be a unique index... SIGH
+ $schema->createIndex('profile_detail', array('profile_id', 'field', 'field_index'));
+ } catch (Exception $e) {
+ common_log(LOG_ERR, __METHOD__ . ': ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * DB_DataObject needs to know about keys that the table has; this function
+ * defines them.
+ *
+ * @return array key definitions
+ */
+
+ function keys()
+ {
+ return array_keys($this->keyTypes());
+ }
+
+ /**
+ * return key definitions for Memcached_DataObject
+ *
+ * Our caching system uses the same key definitions, but uses a different
+ * method to get them.
+ *
+ * @return array key definitions
+ */
+
+ function keyTypes()
+ {
+ // @fixme keys
+ // need a sane key for reverse lookup too
+ return array('id' => 'K');
+ }
+
+ function sequenceKey()
+ {
+ return array('id', true);
+ }
+
+}
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class ExtendedProfile
+{
+ function __construct(Profile $profile)
+ {
+ $this->profile = $profile;
+ $this->sections = $this->getSections();
+ $this->fields = $this->loadFields();
+ }
+
+ function loadFields()
+ {
+ $detail = new Profile_detail();
+ $detail->profile_id = $this->profile->id;
+ $detail->find();
+
+ while ($detail->get()) {
+ $fields[$detail->field][] = clone($detail);
+ }
+ return $fields;
+ }
+
+ function getSections()
+ {
+ return array(
+ 'basic' => array(
+ 'label' => _m('Personal'),
+ 'fields' => array(
+ 'fullname' => array(
+ 'label' => _m('Full name'),
+ 'profile' => 'fullname',
+ 'vcard' => 'fn',
+ ),
+ 'title' => array(
+ 'label' => _m('Title'),
+ 'vcard' => 'title',
+ ),
+ 'manager' => array(
+ 'label' => _m('Manager'),
+ 'type' => 'person',
+ 'vcard' => 'x-manager',
+ ),
+ 'location' => array(
+ 'label' => _m('Location'),
+ 'profile' => 'location'
+ ),
+ 'bio' => array(
+ 'label' => _m('Bio'),
+ 'type' => 'textarea',
+ 'profile' => 'bio',
+ ),
+ 'tags' => array(
+ 'label' => _m('Tags'),
+ 'type' => 'tags',
+ 'profile' => 'tags',
+ ),
+ ),
+ ),
+ 'contact' => array(
+ 'label' => _m('Contact'),
+ 'fields' => array(
+ 'phone' => array(
+ 'label' => _m('Phone'),
+ 'type' => 'phone',
+ 'multi' => true,
+ 'vcard' => 'tel',
+ ),
+ 'im' => array(
+ 'label' => _m('IM'),
+ 'type' => 'im',
+ 'multi' => true,
+ ),
+ 'website' => array(
+ 'label' => _m('Websites'),
+ 'type' => 'website',
+ 'multi' => true,
+ ),
+ ),
+ ),
+ 'personal' => array(
+ 'label' => _m('Personal'),
+ 'fields' => array(
+ 'birthday' => array(
+ 'label' => _m('Birthday'),
+ 'type' => 'date',
+ 'vcard' => 'bday',
+ ),
+ 'spouse' => array(
+ 'label' => _m('Spouse\'s name'),
+ 'vcard' => 'x-spouse',
+ ),
+ 'kids' => array(
+ 'label' => _m('Kids\' names')
+ ),
+ ),
+ ),
+ 'experience' => array(
+ 'label' => _m('Work experience'),
+ 'fields' => array(
+ 'experience' => array(
+ 'type' => 'experience',
+ 'label' => _m('Employer'),
+ ),
+ ),
+ ),
+ 'education' => array(
+ 'label' => _m('Education'),
+ 'fields' => array(
+ 'education' => array(
+ 'type' => 'education',
+ 'label' => _m('Institution'),
+ ),
+ ),
+ ),
+ );
+ }
+}
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class ExtendedProfileWidget extends Widget
+{
+ const EDITABLE=true;
+
+ protected $profile;
+ protected $ext;
+
+ public function __construct(XMLOutputter $out=null, Profile $profile=null, $editable=false)
+ {
+ parent::__construct($out);
+
+ $this->profile = $profile;
+ $this->ext = new ExtendedProfile($this->profile);
+
+ $this->editable = $editable;
+ }
+
+ public function show()
+ {
+ $sections = $this->ext->getSections();
+ foreach ($sections as $name => $section) {
+ $this->showExtendedProfileSection($name, $section);
+ }
+ }
+
+ protected function showExtendedProfileSection($name, $section)
+ {
+ $this->out->element('h3', null, $section['label']);
+ $this->out->elementStart('table', array('class' => 'extended-profile'));
+ foreach ($section['fields'] as $fieldName => $field) {
+ $this->showExtendedProfileField($fieldName, $field);
+ }
+ $this->out->elementEnd('table');
+ }
+
+ protected function showExtendedProfileField($name, $field)
+ {
+ $this->out->elementStart('tr');
+
+ $this->out->element('th', null, $field['label']);
+
+ $this->out->elementStart('td');
+ if ($this->editable) {
+ $this->showEditableField($name, $field);
+ } else {
+ $this->showFieldValue($name, $field);
+ }
+ $this->out->elementEnd('td');
+
+ $this->out->elementEnd('tr');
+ }
+
+ protected function showFieldValue($name, $field)
+ {
+ $this->out->text($name);
+ }
+
+ protected function showEditableField($name, $field)
+ {
+ $out = $this->out;
+ //$out = new HTMLOutputter();
+ // @fixme
+ $type = strval(@$field['type']);
+ $id = "extprofile-" . $name;
+ $value = 'placeholder';
+
+ switch ($type) {
+ case '':
+ case 'text':
+ $out->input($id, null, $value);
+ break;
+ case 'textarea':
+ $out->textarea($id, null, $value);
+ break;
+ default:
+ $out->input($id, null, "TYPE: $type");
+ }
+ }
+}
--- /dev/null
+/* Note the #content is only needed to override weird crap in default styles */
+
+#content table.extended-profile {
+ width: 100%;
+ border-collapse: separate;
+ border-spacing: 8px;
+}
+#content table.extended-profile th {
+ color: #777;
+ background-color: #eee;
+ width: 150px;
+
+ padding-top: 0; /* override bizarre theme defaults */
+
+ text-align: right;
+ padding-right: 8px;
+}
+#content table.extended-profile td {
+ padding: 0; /* override bizarre theme defaults */
+
+ padding-left: 8px;
+}
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class ProfileDetailAction extends ProfileAction
+{
+ function isReadOnly($args)
+ {
+ return true;
+ }
+
+ function title()
+ {
+ return $this->profile->getFancyName();
+ }
+
+ function showLocalNav()
+ {
+ $nav = new PersonalGroupNav($this);
+ $nav->show();
+ }
+
+ function showStylesheets() {
+ parent::showStylesheets();
+ $this->cssLink('plugins/ExtendedProfile/profiledetail.css');
+ return true;
+ }
+
+ function handle($args)
+ {
+ $this->showPage();
+ }
+
+ function showContent()
+ {
+ $cur = common_current_user();
+ if ($cur && $cur->id == $this->profile->id) { // your own page
+ $this->elementStart('div', 'entity_actions');
+ $this->elementStart('li', 'entity_edit');
+ $this->element('a', array('href' => common_local_url('profiledetailsettings'),
+ // TRANS: Link title for link on user profile.
+ 'title' => _m('Edit extended profile settings')),
+ // TRANS: Link text for link on user profile.
+ _m('Edit'));
+ $this->elementEnd('li');
+ $this->elementEnd('div');
+ }
+
+ $widget = new ExtendedProfileWidget($this, $this->profile);
+ $widget->show();
+ }
+}
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class ProfileDetailSettingsAction extends AccountSettingsAction
+{
+
+ function title()
+ {
+ return _m('Extended profile settings');
+ }
+
+ /**
+ * Instructions for use
+ *
+ * @return instructions for use
+ */
+ function getInstructions()
+ {
+ // TRANS: Usage instructions for profile settings.
+ return _('You can update your personal profile info here '.
+ 'so people know more about you.');
+ }
+
+ function showStylesheets() {
+ parent::showStylesheets();
+ $this->cssLink('plugins/ExtendedProfile/profiledetail.css');
+ return true;
+ }
+
+ function handle($args)
+ {
+ $this->showPage();
+ }
+
+ function showContent()
+ {
+ $cur = common_current_user();
+ $profile = $cur->getProfile();
+
+ $widget = new ExtendedProfileWidget($this, $profile, ExtendedProfileWidget::EDITABLE);
+ $widget->show();
+ }
+}
$reply = new Reply();
$reply->notice_id = $notice->id;
$reply->profile_id = $user->id;
+ $reply->modified = $notice->created;
common_log(LOG_INFO, __METHOD__ . ": saving reply: notice {$notice->id} to profile {$user->id}");
$id = $reply->insert();
}
public function testProduction($content, $expected)
{
$rendered = common_render_text($content);
+ // hack!
+ $rendered = preg_replace('/id="attachment-\d+"/', 'id="attachment-XXX"', $rendered);
$this->assertEquals($expected, $rendered);
}
array('file.html',
'file.html'),
array('file.php',
- 'file.php')
+ 'file.php'),
+
+ // scheme-less HTTP URLs with @ in the path: http://status.net/open-source/issues/2248
+ array('http://flickr.com/photos/34807140@N05/3838905434',
+ '<a href="http://flickr.com/photos/34807140@N05/3838905434" title="http://flickr.com/photos/34807140@N05/3838905434" class="attachment thumbnail" id="attachment-XXX" rel="nofollow external">http://flickr.com/photos/34807140@N05/3838905434</a>'),
+ array('flickr.com/photos/34807140@N05/3838905434',
+ '<a href="http://flickr.com/photos/34807140@N05/3838905434" title="http://flickr.com/photos/34807140@N05/3838905434" class="attachment thumbnail" id="attachment-XXX" rel="nofollow external">flickr.com/photos/34807140@N05/3838905434</a>'),
);
}
}