diff --git a/composer.json b/composer.json index 339211f6f..2f12d076d 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,8 @@ }, "require": { "php": ">=5.6.1", + "ext-dom": "*", + "ext-json": "*", "ext-xml": "*", "asika/simple-console": "^1.0", "divineomega/password_exposed": "^2.4", diff --git a/composer.lock b/composer.lock index 16b9e50b5..d0ce1c4e7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ee7a6d8a1a9df21b46478dd91c1b73b7", + "content-hash": "11efc727fd6cae00c1230616e31ad2a2", "packages": [ { "name": "asika/simple-console", @@ -1019,22 +1019,6 @@ "require": { "npm-asset/ev-emitter": ">=1.0.0,<2.0.0" }, - "require-dev": { - "npm-asset/chalk": ">=1.1.1,<2.0.0", - "npm-asset/cheerio": ">=0.19.0,<0.20.0", - "npm-asset/gulp": ">=3.9.0,<4.0.0", - "npm-asset/gulp-jshint": ">=1.11.2,<2.0.0", - "npm-asset/gulp-json-lint": ">=0.1.0,<0.2.0", - "npm-asset/gulp-rename": ">=1.2.2,<2.0.0", - "npm-asset/gulp-replace": ">=0.5.4,<0.6.0", - "npm-asset/gulp-requirejs-optimize": "dev-github:metafizzy/gulp-requirejs-optimize", - "npm-asset/gulp-uglify": ">=1.4.2,<2.0.0", - "npm-asset/gulp-util": ">=3.0.7,<4.0.0", - "npm-asset/highlight.js": ">=8.9.1,<9.0.0", - "npm-asset/marked": ">=0.3.5,<0.4.0", - "npm-asset/minimist": ">=1.2.0,<2.0.0", - "npm-asset/transfob": ">=1.0.0,<2.0.0" - }, "type": "npm-asset-library", "extra": { "npm-asset-bugs": { @@ -1080,14 +1064,6 @@ "reference": null, "shasum": "2736e332aaee73ccf0a14a5f0066391a0a13f4a3" }, - "require-dev": { - "npm-asset/grunt": "~0.4.2", - "npm-asset/grunt-contrib-cssmin": "~0.9.0", - "npm-asset/grunt-contrib-jshint": "~0.6.3", - "npm-asset/grunt-contrib-less": "~0.11.0", - "npm-asset/grunt-contrib-uglify": "~0.4.0", - "npm-asset/grunt-contrib-watch": "~0.6.1" - }, "type": "npm-asset-library", "extra": { "npm-asset-bugs": { @@ -1121,32 +1097,6 @@ "reference": null, "shasum": "2c89d6889b5eac522a7eea32c14521559c6cbf02" }, - "require-dev": { - "npm-asset/commitplease": "2.0.0", - "npm-asset/core-js": "0.9.17", - "npm-asset/grunt": "0.4.5", - "npm-asset/grunt-babel": "5.0.1", - "npm-asset/grunt-cli": "0.1.13", - "npm-asset/grunt-compare-size": "0.4.0", - "npm-asset/grunt-contrib-jshint": "0.11.2", - "npm-asset/grunt-contrib-uglify": "0.9.2", - "npm-asset/grunt-contrib-watch": "0.6.1", - "npm-asset/grunt-git-authors": "2.0.1", - "npm-asset/grunt-jscs": "2.1.0", - "npm-asset/grunt-jsonlint": "1.0.4", - "npm-asset/grunt-npmcopy": "0.1.0", - "npm-asset/gzip-js": "0.3.2", - "npm-asset/jsdom": "5.6.1", - "npm-asset/load-grunt-tasks": "1.0.0", - "npm-asset/qunit-assert-step": "1.0.3", - "npm-asset/qunitjs": "1.17.1", - "npm-asset/requirejs": "2.1.17", - "npm-asset/sinon": "1.10.3", - "npm-asset/sizzle": "2.2.1", - "npm-asset/strip-json-comments": "1.0.3", - "npm-asset/testswarm": "1.1.0", - "npm-asset/win-spawn": "2.0.0" - }, "type": "npm-asset-library", "extra": { "npm-asset-bugs": { @@ -1244,6 +1194,18 @@ "npm-asset/jquery-mousewheel": ">=3.1.13", "npm-asset/php-date-formatter": ">=1.3.4,<2.0.0" }, + "require-dev": { + "npm-asset/chai": ">=4.1.2,<5.0.0", + "npm-asset/concat": "dev-github:azer/concat", + "npm-asset/concat-cli": ">=4.0.0,<5.0.0", + "npm-asset/karma": ">=2.0.0,<3.0.0", + "npm-asset/karma-chai": ">=0.1.0,<0.2.0", + "npm-asset/karma-firefox-launcher": ">=1.1.0,<2.0.0", + "npm-asset/karma-mocha": ">=1.3.0,<2.0.0", + "npm-asset/mocha": ">=5.0.4,<6.0.0", + "npm-asset/uglifycss": ">=0.0.27,<0.0.28", + "npm-asset/uglifyjs": ">=2.4.10,<3.0.0" + }, "type": "npm-asset-library", "extra": { "npm-asset-bugs": { @@ -1297,12 +1259,6 @@ "reference": null, "shasum": "06f0335f16e353a695e7206bf50503cb523a6ee5" }, - "require-dev": { - "npm-asset/grunt": "~0.4.1", - "npm-asset/grunt-contrib-connect": "~0.5.0", - "npm-asset/grunt-contrib-jshint": "~0.7.1", - "npm-asset/grunt-contrib-uglify": "~0.2.7" - }, "type": "npm-asset-library", "extra": { "npm-asset-bugs": { @@ -3607,6 +3563,8 @@ "prefer-lowest": false, "platform": { "php": ">=5.6.1", + "ext-dom": "*", + "ext-json": "*", "ext-xml": "*" }, "platform-dev": [] diff --git a/mod/hovercard.php b/mod/hovercard.php index f5ad3ef02..616064276 100644 --- a/mod/hovercard.php +++ b/mod/hovercard.php @@ -107,7 +107,7 @@ function hovercard_content() 'about' => $contact['about'], 'network' => Strings::formatNetworkName($contact['network'], $contact['url']), 'tags' => $contact['keywords'], - 'bd' => $contact['birthday'] <= '0001-01-01' ? '' : $contact['birthday'], + 'bd' => $contact['birthday'] <= DBA::NULL_DATE ? '' : $contact['birthday'], 'account_type' => Contact::getAccountType($contact), 'actions' => $actions, ]; diff --git a/mod/profiles.php b/mod/profiles.php index 459a1c5e7..fe3b36231 100644 --- a/mod/profiles.php +++ b/mod/profiles.php @@ -216,7 +216,7 @@ function profiles_post(App $a) { } else { $ignore_year = false; } - if (!in_array($dob, ['0000-00-00', '0001-01-01'])) { + if (!in_array($dob, ['0000-00-00', DBA::NULL_DATE])) { if (strpos($dob, '0000-') === 0 || strpos($dob, '0001-') === 0) { $ignore_year = true; $dob = substr($dob, 5); diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 6d435c81c..bb6fc2585 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -740,7 +740,7 @@ class Contact extends BaseObject // "bd" always contains the upcoming birthday of a contact. // "birthday" might contain the birthday including the year of birth. - if ($profile["birthday"] > '0001-01-01') { + if ($profile["birthday"] > DBA::NULL_DATE) { $bd_timestamp = strtotime($profile["birthday"]); $month = date("m", $bd_timestamp); $day = date("d", $bd_timestamp); @@ -757,7 +757,7 @@ class Contact extends BaseObject $profile["bd"] = ( ++$current_year) . "-" . $month . "-" . $day; } } else { - $profile["bd"] = '0001-01-01'; + $profile["bd"] = DBA::NULL_DATE; } } else { $profile = $default; @@ -794,7 +794,7 @@ class Contact extends BaseObject $profile["location"] = ""; $profile["about"] = ""; $profile["gender"] = ""; - $profile["birthday"] = '0001-01-01'; + $profile["birthday"] = DBA::NULL_DATE; } $cache[$url][$uid] = $profile; @@ -1962,44 +1962,33 @@ class Contact extends BaseObject */ public static function updateBirthdays() { - // This only handles foreign or alien networks where a birthday has been provided. - // In-network birthdays are handled within local_delivery + $condition = [ + '`bd` != "" + AND `bd` > "0001-01-01" + AND SUBSTRING(`bd`, 1, 4) != `bdyear` + AND (`contact`.`rel` = ? OR `contact`.`rel` = ?) + AND NOT `contact`.`pending` + AND NOT `contact`.`hidden` + AND NOT `contact`.`blocked` + AND NOT `contact`.`archive` + AND NOT `contact`.`deleted`', + Contact::SHARING, + Contact::FRIEND + ]; - $r = q("SELECT * FROM `contact` WHERE `bd` != '' AND `bd` > '0001-01-01' AND SUBSTRING(`bd`, 1, 4) != `bdyear` "); - if (DBA::isResult($r)) { - foreach ($r as $rr) { - Logger::log('update_contact_birthday: ' . $rr['bd']); + $contacts = DBA::select('contact', ['id', 'uid', 'name', 'url', 'bd'], $condition); - $nextbd = DateTimeFormat::utcNow('Y') . substr($rr['bd'], 4); + while ($contact = DBA::fetch($contacts)) { + Logger::log('update_contact_birthday: ' . $contact['bd']); - /* - * Add new birthday event for this person - * - * $bdtext is just a readable placeholder in case the event is shared - * with others. We will replace it during presentation to our $importer - * to contain a sparkle link and perhaps a photo. - */ - - // Check for duplicates - $condition = ['uid' => $rr['uid'], 'cid' => $rr['id'], - 'start' => DateTimeFormat::utc($nextbd), 'type' => 'birthday']; - if (DBA::exists('event', $condition)) { - continue; - } - - $bdtext = L10n::t('%s\'s birthday', $rr['name']); - $bdtext2 = L10n::t('Happy Birthday %s', ' [url=' . $rr['url'] . ']' . $rr['name'] . '[/url]'); - - q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`,`adjust`) - VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d' ) ", intval($rr['uid']), intval($rr['id']), - DBA::escape(DateTimeFormat::utcNow()), DBA::escape(DateTimeFormat::utcNow()), DBA::escape(DateTimeFormat::utc($nextbd)), - DBA::escape(DateTimeFormat::utc($nextbd . ' + 1 day ')), DBA::escape($bdtext), DBA::escape($bdtext2), DBA::escape('birthday'), - intval(0) - ); + $nextbd = DateTimeFormat::utcNow('Y') . substr($contact['bd'], 4); + if (Event::createBirthday($contact, $nextbd)) { // update bdyear - q("UPDATE `contact` SET `bdyear` = '%s', `bd` = '%s' WHERE `uid` = %d AND `id` = %d", DBA::escape(substr($nextbd, 0, 4)), - DBA::escape($nextbd), intval($rr['uid']), intval($rr['id']) + DBA::update( + 'contact', + ['bdyear' => substr($nextbd, 0, 4), 'bd' => $nextbd], + ['id' => $contact['id']] ); } } diff --git a/src/Model/Event.php b/src/Model/Event.php index 735638287..886f12415 100644 --- a/src/Model/Event.php +++ b/src/Model/Event.php @@ -322,43 +322,48 @@ class Event extends BaseObject // New event. Store it. DBA::insert('event', $event); - $event['id'] = DBA::lastInsertId(); + $item_id = 0; - $item_arr = []; + // Don't create an item for birthday events + if ($event['type'] == 'event') { + $event['id'] = DBA::lastInsertId(); - $item_arr['uid'] = $event['uid']; - $item_arr['contact-id'] = $event['cid']; - $item_arr['uri'] = $event['uri']; - $item_arr['parent-uri'] = $event['uri']; - $item_arr['guid'] = $event['guid']; - $item_arr['plink'] = defaults($arr, 'plink', ''); - $item_arr['post-type'] = Item::PT_EVENT; - $item_arr['wall'] = $event['cid'] ? 0 : 1; - $item_arr['contact-id'] = $contact['id']; - $item_arr['owner-name'] = $contact['name']; - $item_arr['owner-link'] = $contact['url']; - $item_arr['owner-avatar'] = $contact['thumb']; - $item_arr['author-name'] = $contact['name']; - $item_arr['author-link'] = $contact['url']; - $item_arr['author-avatar'] = $contact['thumb']; - $item_arr['title'] = ''; - $item_arr['allow_cid'] = $event['allow_cid']; - $item_arr['allow_gid'] = $event['allow_gid']; - $item_arr['deny_cid'] = $event['deny_cid']; - $item_arr['deny_gid'] = $event['deny_gid']; - $item_arr['private'] = $private; - $item_arr['visible'] = 1; - $item_arr['verb'] = ACTIVITY_POST; - $item_arr['object-type'] = ACTIVITY_OBJ_EVENT; - $item_arr['origin'] = $event['cid'] === 0 ? 1 : 0; - $item_arr['body'] = self::getBBCode($event); - $item_arr['event-id'] = $event['id']; + $item_arr = []; - $item_arr['object'] = '' . XML::escape(ACTIVITY_OBJ_EVENT) . '' . XML::escape($event['uri']) . ''; - $item_arr['object'] .= '' . XML::escape(self::getBBCode($event)) . ''; - $item_arr['object'] .= '' . "\n"; + $item_arr['uid'] = $event['uid']; + $item_arr['contact-id'] = $event['cid']; + $item_arr['uri'] = $event['uri']; + $item_arr['parent-uri'] = $event['uri']; + $item_arr['guid'] = $event['guid']; + $item_arr['plink'] = defaults($arr, 'plink', ''); + $item_arr['post-type'] = Item::PT_EVENT; + $item_arr['wall'] = $event['cid'] ? 0 : 1; + $item_arr['contact-id'] = $contact['id']; + $item_arr['owner-name'] = $contact['name']; + $item_arr['owner-link'] = $contact['url']; + $item_arr['owner-avatar'] = $contact['thumb']; + $item_arr['author-name'] = $contact['name']; + $item_arr['author-link'] = $contact['url']; + $item_arr['author-avatar'] = $contact['thumb']; + $item_arr['title'] = ''; + $item_arr['allow_cid'] = $event['allow_cid']; + $item_arr['allow_gid'] = $event['allow_gid']; + $item_arr['deny_cid'] = $event['deny_cid']; + $item_arr['deny_gid'] = $event['deny_gid']; + $item_arr['private'] = $private; + $item_arr['visible'] = 1; + $item_arr['verb'] = ACTIVITY_POST; + $item_arr['object-type'] = ACTIVITY_OBJ_EVENT; + $item_arr['origin'] = $event['cid'] === 0 ? 1 : 0; + $item_arr['body'] = self::getBBCode($event); + $item_arr['event-id'] = $event['id']; - $item_id = Item::insert($item_arr); + $item_arr['object'] = '' . XML::escape(ACTIVITY_OBJ_EVENT) . '' . XML::escape($event['uri']) . ''; + $item_arr['object'] .= '' . XML::escape(self::getBBCode($event)) . ''; + $item_arr['object'] .= '' . "\n"; + + $item_id = Item::insert($item_arr); + } Addon::callHooks("event_created", $event['id']); } @@ -981,4 +986,47 @@ class Event extends BaseObject return $location; } + + /** + * @brief Add new birthday event for this person + * + * @param array $contact Contact array, expects: id, uid, url, name + * @param string $birthday Birthday of the contact + * @return bool + */ + public static function createBirthday($contact, $birthday) + { + // Check for duplicates + $condition = [ + 'uid' => $contact['uid'], + 'cid' => $contact['id'], + 'start' => DateTimeFormat::utc($birthday), + 'type' => 'birthday' + ]; + if (DBA::exists('event', $condition)) { + return false; + } + + /* + * Add new birthday event for this person + * + * summary is just a readable placeholder in case the event is shared + * with others. We will replace it during presentation to our $importer + * to contain a sparkle link and perhaps a photo. + */ + $values = [ + 'uid' => $contact['uid'], + 'cid' => $contact['id'], + 'start' => DateTimeFormat::utc($birthday), + 'finish' => DateTimeFormat::utc($birthday . ' + 1 day '), + 'summary' => L10n::t('%s\'s birthday', $contact['name']), + 'desc' => L10n::t('Happy Birthday %s', ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'), + 'type' => 'birthday', + 'adjust' => 0 + ]; + + self::store($values); + + return true; + } } diff --git a/src/Model/GContact.php b/src/Model/GContact.php index 1fed1fc3e..3acffb059 100644 --- a/src/Model/GContact.php +++ b/src/Model/GContact.php @@ -864,7 +864,7 @@ class GContact 'location' => $contact['location'], 'about' => $contact['about']]; // Don't update the birthday field if not set or invalid - if (empty($contact['birthday']) || ($contact['birthday'] < '0001-01-01')) { + if (empty($contact['birthday']) || ($contact['birthday'] <= DBA::NULL_DATE)) { unset($fields['bd']); } diff --git a/src/Model/Profile.php b/src/Model/Profile.php index c45bcdb84..61357ef77 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -572,9 +572,18 @@ class Profile if (is_null($r)) { $s = DBA::p( "SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event` - INNER JOIN `contact` ON `contact`.`id` = `event`.`cid` + INNER JOIN `contact` + ON `contact`.`id` = `event`.`cid` + AND (`contact`.`rel` = ? OR `contact`.`rel` = ?) + AND NOT `contact`.`pending` + AND NOT `contact`.`hidden` + AND NOT `contact`.`blocked` + AND NOT `contact`.`archive` + AND NOT `contact`.`deleted` WHERE `event`.`uid` = ? AND `type` = 'birthday' AND `start` < ? AND `finish` > ? ORDER BY `start` ASC ", + Contact::SHARING, + Contact::FRIEND, local_user(), DateTimeFormat::utc('now + 6 days'), DateTimeFormat::utcNow() @@ -749,7 +758,7 @@ class Profile $profile['gender'] = [L10n::t('Gender:'), $a->profile['gender']]; } - if (($a->profile['dob']) && ($a->profile['dob'] > '0001-01-01')) { + if (!empty($a->profile['dob']) && $a->profile['dob'] > DBA::NULL_DATE) { $year_bd_format = L10n::t('j F, Y'); $short_bd_format = L10n::t('j F'); @@ -763,7 +772,7 @@ class Profile } if (!empty($a->profile['dob']) - && $a->profile['dob'] > '0001-01-01' + && $a->profile['dob'] > DBA::NULL_DATE && $age = Temporal::getAgeByTimezone($a->profile['dob'], $a->profile['timezone'], '') ) { $profile['age'] = [L10n::t('Age:'), $age]; diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index ff5081183..b67b7c021 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -16,7 +16,6 @@ use Friendica\Content\Text\BBCode; use Friendica\Content\Text\HTML; use Friendica\Core\Addon; use Friendica\Core\Config; -use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Core\Protocol; use Friendica\Core\System; @@ -690,7 +689,7 @@ class DFRN XML::addElement($doc, $author, "poco:displayName", $profile["name"]); XML::addElement($doc, $author, "poco:updated", $namdate); - if (trim($profile["dob"]) > '0001-01-01') { + if (trim($profile["dob"]) > DBA::NULL_DATE) { XML::addElement($doc, $author, "poco:birthday", "0000-".date("m-d", strtotime($profile["dob"]))); } @@ -1510,43 +1509,6 @@ class DFRN return intval($res->status); } - /** - * @brief Add new birthday event for this person - * - * @param array $contact Contact record - * @param string $birthday Birthday of the contact - * @return void - * @todo Add array type-hint for $contact - */ - private static function birthdayEvent($contact, $birthday) - { - // Check for duplicates - $condition = ['uid' => $contact['uid'], 'cid' => $contact['id'], - 'start' => DateTimeFormat::utc($birthday), 'type' => 'birthday']; - if (DBA::exists('event', $condition)) { - return; - } - - Logger::log('updating birthday: ' . $birthday . ' for contact ' . $contact['id']); - - $bdtext = L10n::t('%s\'s birthday', $contact['name']); - $bdtext2 = L10n::t('Happy Birthday %s', ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'); - - $r = q( - "INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`) - VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", - intval($contact['uid']), - intval($contact['id']), - DBA::escape(DateTimeFormat::utcNow()), - DBA::escape(DateTimeFormat::utcNow()), - DBA::escape(DateTimeFormat::utc($birthday)), - DBA::escape(DateTimeFormat::utc($birthday . ' + 1 day ')), - DBA::escape($bdtext), - DBA::escape($bdtext2), - DBA::escape('birthday') - ); - } - /** * @brief Fetch the author data from head or entry items * @@ -1722,9 +1684,9 @@ class DFRN // "poco:birthday" is the birthday in the format "yyyy-mm-dd" $value = XML::getFirstNodeValue($xpath, $element . "/poco:birthday/text()", $context); - if (!in_array($value, ["", "0000-00-00", "0001-01-01"])) { + if (!in_array($value, ["", "0000-00-00", DBA::NULL_DATE])) { $bdyear = date("Y"); - $value = str_replace("0000", $bdyear, $value); + $value = str_replace(["0000", "0001"], $bdyear, $value); if (strtotime($value) < time()) { $value = str_replace($bdyear, $bdyear + 1, $value); @@ -1737,7 +1699,7 @@ class DFRN $contact = array_merge($contact_old, $poco); if ($contact_old["bdyear"] != $contact["bdyear"]) { - self::birthdayEvent($contact, $birthday); + Event::createBirthday($contact, $birthday); } // Get all field names