QvitterPlugin.php 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505
  1. <?php
  2. /* · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
  3. · ·
  4. · ·
  5. · Q V I T T E R ·
  6. · ·
  7. · https://git.gnu.io/h2p/Qvitter ·
  8. · ·
  9. · ·
  10. · <o) ·
  11. · /_//// ·
  12. · (____/ ·
  13. · (o< ·
  14. · o> \\\\_\ ·
  15. · \\) \____) ·
  16. · ·
  17. · ·
  18. · ·
  19. · Qvitter is free software: you can redistribute it and / or modify it ·
  20. · under the terms of the GNU Affero General Public License as published by ·
  21. · the Free Software Foundation, either version three of the License or (at ·
  22. · your option) any later version. ·
  23. · ·
  24. · Qvitter is distributed in hope that it will be useful but WITHOUT ANY ·
  25. · WARRANTY; without even the implied warranty of MERCHANTABILTY or FITNESS ·
  26. · FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for ·
  27. · more details. ·
  28. · ·
  29. · You should have received a copy of the GNU Affero General Public License ·
  30. · along with Qvitter. If not, see <http://www.gnu.org/licenses/>. ·
  31. · ·
  32. · Contact h@nnesmannerhe.im if you have any questions. ·
  33. · ·
  34. · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */
  35. const QVITTERDIR = __DIR__;
  36. class QvitterPlugin extends Plugin {
  37. protected $hijack_ui = false;
  38. protected $qvitter_hide_replies = false;
  39. static function settings($setting)
  40. {
  41. /* · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
  42. · ·
  43. · S E T T I N G S ·
  44. · ·
  45. · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */
  46. // THESE SETTINGS CAN BE OVERRIDDEN IN CONFIG.PHP
  47. // e.g. $config['site']['qvitter']['enabledbydefault'] = false;
  48. // ENABLED BY DEFAULT (true/false)
  49. $settings['enabledbydefault'] = true;
  50. // DEFAULT BACKGROUND COLOR
  51. $settings['defaultbackgroundcolor'] = '#f4f4f4';
  52. // DEFAULT BACKGROUND IMAGE
  53. $settings['sitebackground'] = 'img/vagnsmossen.jpg';
  54. // FAVICON PATH (we've used realfavicongenerator.net to generate the icons)
  55. $settings['favicon_path'] = Plugin::staticPath('Qvitter', '').'img/gnusocial-favicons/';
  56. // DEFAULT SPRITE
  57. $settings['sprite'] = Plugin::staticPath('Qvitter', '').'img/sprite.png?v=41';
  58. // DEFAULT LINK COLOR
  59. $settings['defaultlinkcolor'] = '#0084B4';
  60. // ENABLE DEFAULT WELCOME TEXT
  61. $settings['enablewelcometext'] = true;
  62. // CUSTOM WELCOME TEXT (overrides the previous setting)
  63. $settings['customwelcometext'] = false;
  64. // Example:
  65. // $settings['customwelcometext']['sv'] = '<h1>Välkommen till Quitter.se – en federerad<sup>1</sup> mikrobloggsallmänning!</h1><p>Etc etc...</p>';
  66. // $settings['customwelcometext']['en'] = '<h1>Welcome to Quitter.se – a federated microblog common!</h1><p>Etc etc...</p>';
  67. // TIME BETWEEN POLLING
  68. $settings['timebetweenpolling'] = 5000; // ms
  69. // URL SHORTENER
  70. $settings['urlshortenerapiurl'] = 'http://qttr.at/yourls-api.php';
  71. $settings['urlshortenersignature'] = 'b6afeec983';
  72. $settings['urlshortenerformat'] = 'jsonp'; // if you're using shortener.php you can set this to 'json', which enables you to use YOURLS versions below 1.5.1
  73. // CUSTOM TERMS OF USE
  74. $settings['customtermsofuse'] = false;
  75. // IP ADDRESSES BLOCKED FROM REGISTRATION
  76. $settings['blocked_ips'] = array();
  77. // LINKIFY DOMAINS WITHOUT PROTOCOL AS DEFAULT
  78. $settings['linkify_bare_domains'] = true;
  79. /* · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
  80. · ·
  81. · (o> >o) ·
  82. · \\\\_\ /_//// .
  83. · \____) (____/ ·
  84. · ·
  85. · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */
  86. // config.php settings override the settings in this file
  87. $configphpsettings = common_config('site','qvitter') ?: array();
  88. foreach($configphpsettings as $configphpsetting=>$value) {
  89. $settings[$configphpsetting] = $value;
  90. }
  91. // set linkify setting
  92. common_config_set('linkify', 'bare_domains', $settings['linkify_bare_domains']);
  93. if(isset($settings[$setting])) {
  94. return $settings[$setting];
  95. }
  96. else {
  97. return false;
  98. }
  99. }
  100. public function initialize()
  101. {
  102. // show qvitter link in the admin panel
  103. common_config_append('admin', 'panels', 'qvitteradm');
  104. }
  105. function onCheckSchema(): bool
  106. {
  107. $schema = Schema::get();
  108. // make sure we have a notifications table
  109. $schema->ensureTable('qvitternotification', QvitterNotification::schemaDef());
  110. // index the url column in the notice table
  111. $notice_schemadef = $schema->getTableDef('notice');
  112. if(!isset($notice_schemadef['indexes']['notice_url_idx'])) {
  113. try {
  114. $schema->createIndex('notice', 'url');
  115. } catch (Exception $e) {
  116. common_log(LOG_ERR, __METHOD__ . ': ' . $e->getMessage());
  117. }
  118. }
  119. return true;
  120. }
  121. public function onBeforePluginCheckSchema(): bool
  122. {
  123. QvitterNotification::beforeSchemaUpdate();
  124. return true;
  125. }
  126. // route/reroute urls
  127. public function onRouterInitialized($m)
  128. {
  129. $m->connect('api/qvitter/statuses/user_timeline.:format',
  130. array('action' => 'ApiQvitterTimelineUser',
  131. 'format' => '(xml|json|rss|atom|as)'));
  132. $m->connect(':nickname/mutes',
  133. array('action' => 'qvitter',
  134. 'nickname' => Nickname::INPUT_FMT));
  135. $m->connect('api/qvitter/mutes.json',
  136. array('action' => 'ApiQvitterMutes'));
  137. $m->connect('api/qvitter/sandboxed.:format',
  138. array('action' => 'ApiQvitterSandboxed',
  139. 'format' => '(xml|json)'));
  140. $m->connect('api/qvitter/silenced.:format',
  141. array('action' => 'ApiQvitterSilenced',
  142. 'format' => '(xml|json)'));
  143. $m->connect('api/qvitter/sandbox/create.json',
  144. array('action' => 'ApiQvitterSandboxCreate'));
  145. $m->connect('api/qvitter/sandbox/destroy.json',
  146. array('action' => 'ApiQvitterSandboxDestroy'));
  147. $m->connect('api/qvitter/silence/create.json',
  148. array('action' => 'ApiQvitterSilenceCreate'));
  149. $m->connect('api/qvitter/silence/destroy.json',
  150. array('action' => 'ApiQvitterSilenceDestroy'));
  151. $m->connect('services/oembed.:format',
  152. array('action' => 'apiqvitteroembednotice',
  153. 'format' => '(xml|json)'));
  154. $m->connect('api/qvitter/check_email.json',
  155. array('action' => 'ApiQvitterCheckEmail'));
  156. $m->connect('api/qvitter/:nickname/lists/:id/subscribers.json',
  157. array('action' => 'ApiQvitterListSubscribers',
  158. 'nickname' => '[a-zA-Z0-9]+',
  159. 'id' => '[a-zA-Z0-9]+'));
  160. $m->connect('api/qvitter/:nickname/lists/:id/members.json',
  161. array('action' => 'ApiQvitterListMembers',
  162. 'nickname' => '[a-zA-Z0-9]+',
  163. 'id' => '[a-zA-Z0-9]+'));
  164. $m->connect('api/qvitter/:nickname/lists/:id/statuses.json',
  165. array('action' => 'ApiQvitterTimelineList',
  166. 'nickname' => '[a-zA-Z0-9]+',
  167. 'id' => '[a-zA-Z0-9]+'));
  168. $m->connect('api/qvitter/blocks.json',
  169. array('action' => 'ApiQvitterBlocks'));
  170. $m->connect('api/qvitter/hello.json',
  171. array('action' => 'ApiQvitterHello'));
  172. $m->connect('api/qvitter/mark_all_notifications_as_seen.json',
  173. array('action' => 'ApiQvitterMarkAllNotificationsAsSeen'));
  174. $m->connect('api/qvitter/favs_and_repeats/:notice_id.json',
  175. array('action' => 'ApiFavsAndRepeats'),
  176. array('notice_id' => '[0-9]+'));
  177. $m->connect('api/statuses/public_and_external_timeline.:format',
  178. array('action' => 'ApiTimelinePublicAndExternal',
  179. 'format' => '(xml|json|rss|atom|as)'));
  180. $m->connect('api/qvitter/update_bookmarks.json',
  181. array('action' => 'ApiQvitterUpdateBookmarks'));
  182. $m->connect('api/qvitter/set_profile_pref.json',
  183. array('action' => 'ApiQvitterSetProfilePref'));
  184. $m->connect('api/qvitter/update_link_color.json',
  185. array('action' => 'apiqvitterupdatelinkcolor'));
  186. $m->connect('api/qvitter/update_background_color.json',
  187. array('action' => 'apiqvitterupdatebackgroundcolor'));
  188. $m->connect('api/qvitter/checklogin.json',
  189. array('action' => 'apiqvitterchecklogin'));
  190. $m->connect('api/qvitter/allfollowing/:id.json',
  191. array('action' => 'apiqvitterallfollowing',
  192. 'id' => Nickname::INPUT_FMT));
  193. $m->connect('api/account/update_profile_banner.json',
  194. array('action' => 'ApiAccountUpdateProfileBanner'));
  195. $m->connect('api/saved_searches/list.json',
  196. array('action' => 'ApiSavedSearchesList'));
  197. $m->connect('api/trends/place.json',
  198. array('action' => 'ApiTrendsPlace'));
  199. $m->connect('api/activity/about_me/unread.json',
  200. array('action' => 'ApiActivityAboutMeUnread'));
  201. $m->connect('api/qvitter/update_background_image.json',
  202. array('action' => 'ApiUpdateBackgroundImage'));
  203. $m->connect('api/qvitter/update_avatar.json',
  204. array('action' => 'ApiUpdateAvatar'));
  205. $m->connect('api/qvitter/upload_image.json',
  206. array('action' => 'ApiUploadImage'));
  207. $m->connect('api/qvitter/external_user_show.json',
  208. array('action' => 'ApiExternalUserShow'));
  209. $m->connect('api/qvitter/toggle_qvitter.json',
  210. array('action' => 'ApiToggleQvitter'));
  211. $m->connect('api/qvitter/statuses/notifications.json',
  212. array('action' => 'apiqvitternotifications'));
  213. $m->connect(':nickname/notifications',
  214. array('action' => 'qvitter',
  215. 'nickname' => Nickname::INPUT_FMT));
  216. $m->connect(':nickname/blocks',
  217. array('action' => 'qvitter',
  218. 'nickname' => Nickname::INPUT_FMT));
  219. $m->connect('settings/qvitter',
  220. array('action' => 'qvittersettings'));
  221. $m->connect('panel/qvitter',
  222. array('action' => 'qvitteradminsettings'));
  223. $m->connect('main/qlogin',
  224. array('action' => 'qvitterlogin'));
  225. $m->connect('api/qvitter/statuses/update.:format',
  226. array('action' => 'ApiQvitterStatusesUpdate'),
  227. array('format' => '(xml|json)'));
  228. // check if we should reroute UI to qvitter, and which home-stream the user wants (hide-replies or normal)
  229. $scoped = Profile::current();
  230. $qvitter_enabled_by_user = 0;
  231. $qvitter_disabled_by_user = 0;
  232. if ($scoped instanceof Profile) {
  233. $qvitter_enabled_by_user = (int)$scoped->getPref('qvitter', 'enable_qvitter', false);
  234. $qvitter_disabled_by_user = (int)$scoped->getPref('qvitter', 'disable_qvitter', false);
  235. $this->qvitter_hide_replies = $scoped->getPref('qvitter', 'hide_replies', false);
  236. }
  237. // reroute to qvitter if we're logged out and qvitter is enabled by default
  238. if(static::settings('enabledbydefault') === true && is_null($scoped)) {
  239. $this->hijack_ui = true;
  240. }
  241. // if we're logged in and qvitter is enabled by default, reroute if the user has not disabled qvitter
  242. elseif(static::settings('enabledbydefault') === true && $qvitter_disabled_by_user == 0){
  243. $this->hijack_ui = true;
  244. }
  245. // if we're logged in, and qvitter is _not_ enabled by default, reroute if the user enabled qvitter
  246. elseif(static::settings('enabledbydefault') === false && $qvitter_enabled_by_user == 1) {
  247. $this->hijack_ui = true;
  248. }
  249. if ($this->hijack_ui === true) {
  250. // other plugins might want to reroute to qvitter
  251. Event::handle('QvitterHijackUI', array($m));
  252. $m->connect('', array('action' => 'qvitter'));
  253. $m->connect('main/all', array('action' => 'qvitter'));
  254. $m->connect('main/public', array('action' => 'qvitter'));
  255. $m->connect('main/silenced', array('action' => 'qvitter'));
  256. $m->connect('main/sandboxed', array('action' => 'qvitter'));
  257. $m->connect('search/notice', array('action' => 'qvitter'));
  258. // if the user wants the twitter style home stream with hidden replies to non-friends
  259. if ($this->qvitter_hide_replies == 1) {
  260. URLMapperOverwrite::overwrite_variable($m, 'api/statuses/friends_timeline.:format',
  261. array('action' => 'ApiTimelineFriends'),
  262. array('format' => '(xml|json|rss|atom|as)'),
  263. 'ApiTimelineFriendsHiddenReplies');
  264. }
  265. URLMapperOverwrite::overwrite_variable($m, ':nickname',
  266. array('action' => 'showstream'),
  267. array('nickname' => Nickname::DISPLAY_FMT),
  268. 'qvitter');
  269. URLMapperOverwrite::overwrite_variable($m, ':nickname/',
  270. array('action' => 'showstream'),
  271. array('nickname' => Nickname::DISPLAY_FMT),
  272. 'qvitter');
  273. URLMapperOverwrite::overwrite_variable($m, ':nickname/all',
  274. array('action' => 'all'),
  275. array('nickname' => Nickname::DISPLAY_FMT),
  276. 'qvitter');
  277. URLMapperOverwrite::overwrite_variable($m, ':nickname/subscriptions',
  278. array('action' => 'subscriptions'),
  279. array('nickname' => Nickname::DISPLAY_FMT),
  280. 'qvitter');
  281. URLMapperOverwrite::overwrite_variable($m, ':nickname/subscribers',
  282. array('action' => 'subscribers'),
  283. array('nickname' => Nickname::DISPLAY_FMT),
  284. 'qvitter');
  285. URLMapperOverwrite::overwrite_variable($m, ':nickname/groups',
  286. array('action' => 'usergroups'),
  287. array('nickname' => Nickname::DISPLAY_FMT),
  288. 'qvitter');
  289. URLMapperOverwrite::overwrite_variable($m, ':nickname/replies',
  290. array('action' => 'replies'),
  291. array('nickname' => Nickname::DISPLAY_FMT),
  292. 'qvitter');
  293. URLMapperOverwrite::overwrite_variable($m, ':nickname/favorites',
  294. array('action' => 'showfavorites'),
  295. array('nickname' => Nickname::DISPLAY_FMT),
  296. 'qvitter');
  297. URLMapperOverwrite::overwrite_variable($m, 'group/:nickname',
  298. array('action' => 'showgroup'),
  299. array('nickname' => Nickname::DISPLAY_FMT),
  300. 'qvitter');
  301. URLMapperOverwrite::overwrite_variable($m, 'group/:nickname/members',
  302. array('action' => 'groupmembers'),
  303. array('nickname' => Nickname::DISPLAY_FMT),
  304. 'qvitter');
  305. URLMapperOverwrite::overwrite_variable($m, ':nickname/all/:tag',
  306. array('action' => 'showprofiletag'),
  307. array('nickname' => Nickname::DISPLAY_FMT,
  308. 'tag' => Router::REGEX_TAG),
  309. 'qvitter');
  310. URLMapperOverwrite::overwrite_variable($m, ':tagger/all/:tag/tagged',
  311. array('action' => 'peopletagged'),
  312. array('tagger' => Nickname::DISPLAY_FMT,
  313. 'tag' => Router::REGEX_TAG),
  314. 'qvitter');
  315. URLMapperOverwrite::overwrite_variable($m, ':tagger/all/:tag/subscribers',
  316. array('action' => 'peopletagsubscribers'),
  317. array('tagger' => Nickname::DISPLAY_FMT,
  318. 'tag' => Router::REGEX_TAG),
  319. 'qvitter');
  320. $m->connect('group/:nickname/admins',
  321. array('action' => 'qvitter'),
  322. array('nickname' => Nickname::DISPLAY_FMT));
  323. URLMapperOverwrite::overwrite_variable($m, 'tag/:tag',
  324. array('action' => 'showstream'),
  325. array('tag' => Router::REGEX_TAG),
  326. 'qvitter');
  327. URLMapperOverwrite::overwrite_variable($m, 'notice/:notice',
  328. array('action' => 'shownotice'),
  329. array('notice' => '[0-9]+'),
  330. 'qvitter');
  331. URLMapperOverwrite::overwrite_variable($m, 'user/:id',
  332. array('action' => 'userbyid'),
  333. array('id' => '[0-9]+'),
  334. 'qvitter');
  335. URLMapperOverwrite::overwrite_variable($m, 'conversation/:id',
  336. array('action' => 'conversation'),
  337. array('id' => '[0-9]+'),
  338. 'qvitter');
  339. }
  340. // if qvitter is opt-out, disable the default register page (if we don't have a valid invitation code)
  341. $valid_code = isset($_POST['code'])
  342. ? Invitation::getKV('code', $_POST['code'])
  343. : null;
  344. if(self::settings('enabledbydefault') && empty($valid_code)) {
  345. $m->connect('main/register',
  346. array('action' => 'qvitter'));
  347. }
  348. // add user arrays for some urls, to use to build profile cards
  349. // this way we don't have to request this in a separate http request
  350. if(isset($_GET['withuserarray'])) switch (getPath($_REQUEST)) {
  351. case 'api/statuses/followers.json':
  352. case 'api/statuses/friends.json':
  353. case 'api/statusnet/groups/list.json':
  354. case 'api/statuses/mentions.json':
  355. case 'api/favorites.json':
  356. case 'api/statuses/friends_timeline.json':
  357. case 'api/statuses/user_timeline.json':
  358. case 'api/qvitter/statuses/user_timeline.json':
  359. // add logged in user's user array
  360. if (common_logged_in() && !isset($_GET['screen_name']) && !isset($_GET['id'])) {
  361. $profilecurrent = Profile::current();
  362. header('Qvitter-User-Array: '.json_encode($this->qvitterTwitterUserArray($profilecurrent)));
  363. }
  364. // add screen_name's user array
  365. elseif(isset($_GET['screen_name']) || isset($_GET['id'])){
  366. if(isset($_GET['screen_name'])) {
  367. $user = User::getKV('nickname', $_GET['screen_name']);
  368. if($user instanceof User) {
  369. $profile = $user->getProfile();
  370. }
  371. }
  372. elseif(isset($_GET['id'])) {
  373. $profile = Profile::getKV('id', $_GET['id']);
  374. $user = User::getKV('id', $_GET['id']);
  375. }
  376. if(isset($profile) && $profile instanceof Profile) {
  377. header('Qvitter-User-Array: '.json_encode($this->qvitterTwitterUserArray($profile)));
  378. }
  379. }
  380. break;
  381. }
  382. }
  383. /**
  384. * Add script to default ui, to be able to toggle Qvitter with one click
  385. *
  386. * @return boolean hook return
  387. */
  388. function onEndShowScripts($action){
  389. if (common_logged_in()) {
  390. $user = common_current_user();
  391. $profile = $user->getProfile();
  392. $qvitter_enabled='false';
  393. // if qvitter is enabled by default but _not_ disabled by the user,
  394. if(QvitterPlugin::settings('enabledbydefault')) {
  395. $disabled = Profile_prefs::getConfigData($profile, 'qvitter', 'disable_qvitter');
  396. if($disabled == 0) {
  397. $qvitter_enabled='true';
  398. }
  399. }
  400. // if qvitter is disabled by default and _enabled_ by the user,
  401. else {
  402. $enabled = Profile_prefs::getConfigData($profile, 'qvitter', 'enable_qvitter');
  403. if($enabled == 1) {
  404. $qvitter_enabled='true';
  405. }
  406. }
  407. $action->inlineScript(' var toggleQvitterAPIURL = \''.common_path('', true).'api/qvitter/toggle_qvitter.json\';
  408. var toggleText = \''._('New').' '.str_replace("'","\'",common_config('site','name')).'\';
  409. var qvitterEnabled = '.$qvitter_enabled.';
  410. var qvitterAllLink = \''.common_local_url('all', array('nickname' => $user->nickname)).'\';
  411. ');
  412. $action->script($this->path('js/toggleqvitter.js').'?changed='.date('YmdHis',filemtime(QVITTERDIR.'/js/toggleqvitter.js')));
  413. }
  414. }
  415. /**
  416. * Menu item for Qvitter
  417. *
  418. * @param Action $action action being executed
  419. *
  420. * @return boolean hook return
  421. */
  422. function onEndAccountSettingsNav($action): bool
  423. {
  424. $action_name = $action->trimmed('action');
  425. $action->menuItem(common_local_url('qvittersettings'),
  426. // TRANS: Poll plugin menu item on user settings page.
  427. _m('MENU', 'Qvitter'),
  428. // TRANS: Poll plugin tooltip for user settings menu item.
  429. _m('Qvitter Settings'),
  430. $action_name === 'qvittersettings');
  431. return true;
  432. }
  433. /**
  434. * Menu item for admin panel
  435. *
  436. * @param Action $action action being executed
  437. *
  438. * @return boolean hook return
  439. */
  440. function onEndAdminPanelNav($action): bool
  441. {
  442. $action_name = $action->trimmed('action');
  443. $action->out->menuItem(common_local_url('qvitteradminsettings'),
  444. // TRANS: Poll plugin menu item on user settings page.
  445. _m('MENU', 'Qvitter'),
  446. // TRANS: Poll plugin tooltip for user settings menu item.
  447. _m('Qvitter Sidebar Notice'),
  448. $action_name === 'qvitteradminsettings');
  449. return true;
  450. }
  451. /**
  452. * Add stuff to notices in API responses
  453. *
  454. * @param Action $action action being executed
  455. *
  456. * @return boolean hook return
  457. */
  458. function onNoticeSimpleStatusArray($notice, &$twitter_status, $scoped)
  459. {
  460. // strip tags from source, we can't trust html here, because of gs bug
  461. $twitter_status['source'] = htmlspecialchars(strip_tags($twitter_status['source']));
  462. // groups
  463. $notice_groups = $notice->getGroups();
  464. $group_addressees = false;
  465. foreach($notice_groups as $g) {
  466. $group_addressees[] = array('nickname'=>$g->nickname,'url'=>$g->mainpage);
  467. }
  468. $twitter_status['statusnet_in_groups'] = $group_addressees;
  469. // for older verions of gnu social: include the repeat-id, which we need when unrepeating later
  470. if(array_key_exists('repeated', $twitter_status) && $twitter_status['repeated'] === true) {
  471. $repeated = Notice::pkeyGet(array('profile_id' => $scoped->id,
  472. 'repeat_of' => $notice->id,
  473. 'verb' => 'http://activitystrea.ms/schema/1.0/share'));
  474. $twitter_status['repeated_id'] = $repeated->id;
  475. }
  476. // more metadata about attachments
  477. // get all attachments first, and put all the extra meta data in an array
  478. $attachments = $notice->attachments();
  479. $attachment_url_to_id = array();
  480. if (!empty($attachments)) {
  481. foreach ($attachments as $attachment) {
  482. if(is_object($attachment)) {
  483. try {
  484. $enclosure_o = $attachment->getEnclosure();
  485. // Oembed
  486. if(array_key_exists('Oembed', GNUsocial::getActiveModules())) {
  487. $oembed = File_oembed::getKV('file_id',$attachment->id);
  488. if($oembed instanceof File_oembed) {
  489. $oembed_html = str_replace('&lt;!--//--&gt;','',$oembed->html); // trash left of wordpress' javascript after htmLawed removed the tags
  490. if($oembed->provider == 'Twitter' && strstr($oembed_html, '>— '.$oembed->author_name)) {
  491. $oembed_html = substr($oembed_html,0,strpos($oembed_html, '>— '.$oembed->author_name)+1); // remove user data from twitter oembed html (we have it in )
  492. $twitter_username = substr($oembed->html,strpos($oembed->html, '>— '.$oembed->author_name)+strlen('>— '.$oembed->author_name));
  493. $twitter_username = substr($twitter_username, strpos($twitter_username,'(@')+1);
  494. $twitter_username = substr($twitter_username, 0,strpos($twitter_username,')'));
  495. $oembed->title = $twitter_username;
  496. }
  497. $oembed_html = str_replace('&#8230;','...',$oembed_html); // ellipsis is sometimes stored as html in db, for some reason
  498. $oembed_html = mb_substr(trim(strip_tags($oembed_html)),0,250);
  499. $oembed_title = trim(strip_tags(html_entity_decode($oembed->title,ENT_QUOTES))); // sometimes we have html charachters that we want to decode and then strip
  500. $oembed_provider = trim(strip_tags(html_entity_decode($oembed->provider,ENT_QUOTES)));
  501. $oembed_author_name = trim(strip_tags(html_entity_decode($oembed->author_name,ENT_QUOTES)));
  502. $attachment_url_to_id[$enclosure_o->url]['oembed'] = array(
  503. 'type'=> $oembed->type,
  504. 'provider'=> $oembed_provider,
  505. 'provider_url'=> $oembed->provider_url,
  506. 'oembedHTML'=> $oembed_html,
  507. 'title'=> $oembed_title,
  508. 'author_name'=> $oembed_author_name,
  509. 'author_url'=> $oembed->author_url
  510. );
  511. } else {
  512. $attachment_url_to_id[$enclosure_o->url]['oembed'] = false;
  513. }
  514. }
  515. // add id to all attachments
  516. $attachment_url_to_id[$enclosure_o->url]['id'] = $attachment->id;
  517. // add an attachment version to all attachments
  518. // this means we can force all cached attachments to update, if we change this
  519. $attachment_url_to_id[$enclosure_o->url]['version'] = '1.2';
  520. // add data about thumbnails
  521. $thumb = $attachment->getThumbnail();
  522. $large_thumb = $attachment->getThumbnail(1000,3000,false);
  523. if(method_exists('File_thumbnail','url')) {
  524. $thumb_url = File_thumbnail::url($thumb->filename);
  525. $large_thumb_url = File_thumbnail::url($large_thumb->filename);
  526. } else {
  527. $thumb_url = $thumb->getUrl();
  528. $large_thumb_url = $large_thumb->getUrl();
  529. }
  530. $attachment_url_to_id[$enclosure_o->url]['thumb_url'] = $thumb_url;
  531. $attachment_url_to_id[$enclosure_o->url]['large_thumb_url'] = $large_thumb_url;
  532. $attachment_url_to_id[$enclosure_o->url]['width'] = $attachment->width;
  533. $attachment_url_to_id[$enclosure_o->url]['height'] = $attachment->height;
  534. // animated gif?
  535. if($attachment->mimetype == 'image/gif') {
  536. $image = ImageFile::fromFileObject($attachment);
  537. if($image->animated == 1) {
  538. $attachment_url_to_id[$enclosure_o->url]['animated'] = true;
  539. } else {
  540. $attachment_url_to_id[$enclosure_o->url]['animated'] = false;
  541. }
  542. }
  543. // this applies to older versions of gnu social, i think
  544. } catch (Exception $e) {
  545. $twitter_status['attachment_error'] = array('code'=>$e->getCode(),'message'=>$e->getMessage(),'file'=>$e->getFile(),'line'=>$e->getLine(),'trace'=>$e->getTraceAsString());
  546. $thumb = File_thumbnail::getKV('file_id', $attachment->id);
  547. if ($thumb instanceof File_thumbnail) {
  548. $thumb_url = $thumb->getUrl();
  549. $attachment_url_to_id[$enclosure_o->url]['id'] = $attachment->id;
  550. $attachment_url_to_id[$enclosure_o->url]['thumb_url'] = $thumb_url;
  551. $attachment_url_to_id[$enclosure_o->url]['large_thumb_url'] = $thumb_url;
  552. $attachment_url_to_id[$enclosure_o->url]['width'] = $attachment->width;
  553. $attachment_url_to_id[$enclosure_o->url]['height'] = $attachment->height;
  554. // animated gif?
  555. if($attachment->mimetype == 'image/gif') {
  556. $image = ImageFile::fromFileObject($attachment);
  557. if($image->animated == 1) {
  558. $attachment_url_to_id[$enclosure_o->url]['animated'] = true;
  559. }
  560. else {
  561. $attachment_url_to_id[$enclosure_o->url]['animated'] = false;
  562. }
  563. }
  564. }
  565. }
  566. }
  567. }
  568. }
  569. // add the extra meta data to $twitter_status
  570. if (!empty($twitter_status['attachments'])) {
  571. foreach ($twitter_status['attachments'] as &$attachment) {
  572. if (!empty($attachment_url_to_id[$attachment['url']])) {
  573. $attachment = array_merge($attachment,$attachment_url_to_id[$attachment['url']]);
  574. }
  575. }
  576. }
  577. // quoted notices
  578. if (!empty($twitter_status['attachments'])) {
  579. foreach ($twitter_status['attachments'] as &$attachment) {
  580. $quoted_notice = false;
  581. // if this attachment has an url this might be a notice url
  582. if (isset($attachment['url'])) {
  583. $noticeurl = common_path('notice/', GNUsocial::isHTTPS());
  584. $instanceurl = common_path('', GNUsocial::isHTTPS());
  585. // remove protocol for the comparison below
  586. $noticeurl_wo_protocol = preg_replace('(^https?://)', '', $noticeurl);
  587. $instanceurl_wo_protocol = preg_replace('(^https?://)', '', $instanceurl);
  588. $attachment_url_wo_protocol = preg_replace('(^https?://)', '', $attachment['url']);
  589. // local notice urls
  590. if(strpos($attachment_url_wo_protocol, $noticeurl_wo_protocol) === 0) {
  591. $possible_notice_id = str_replace($noticeurl_wo_protocol,'',$attachment_url_wo_protocol);
  592. if(ctype_digit($possible_notice_id)) {
  593. $quoted_notice = Notice::getKV('id',$possible_notice_id);;
  594. }
  595. }
  596. // remote. but we don't want to lookup every url in the db,
  597. // so only do this if we have reason to believe this might
  598. // be a remote notice url
  599. elseif(strpos($attachment_url_wo_protocol, $instanceurl_wo_protocol) !== 0 && stristr($attachment_url_wo_protocol,'/notice/')) {
  600. $quoted_notice = Notice::getKV('url',$attachment['url']);
  601. // try with http<->https if no match. applies to quitter.se notices mostly
  602. if(!$quoted_notice instanceof Notice) {
  603. if(strpos($attachment['url'],'https://') === 0) {
  604. $quoted_notice = Notice::getKV('url',str_replace('https://','http://', $attachment['url']));
  605. } else {
  606. $quoted_notice = Notice::getKV('url',str_replace('http://','https://', $attachment['url']));
  607. }
  608. }
  609. }
  610. // include the quoted notice in the attachment
  611. if($quoted_notice instanceof Notice) {
  612. $quoted_notice_author = Profile::getKV('id',$quoted_notice->profile_id);
  613. if($quoted_notice_author instanceof Profile) {
  614. $attachment['quoted_notice']['id'] = $quoted_notice->id;
  615. $attachment['quoted_notice']['content'] = $quoted_notice->content;
  616. $attachment['quoted_notice']['nickname'] = $quoted_notice_author->nickname;
  617. $attachment['quoted_notice']['fullname'] = $quoted_notice_author->fullname;
  618. $attachment['quoted_notice']['is_local'] = $quoted_notice_author->isLocal();
  619. $quoted_notice_attachments = $quoted_notice->attachments();
  620. foreach($quoted_notice_attachments as $q_attach) {
  621. if(is_object($q_attach)) {
  622. try {
  623. $qthumb = $q_attach->getThumbnail();
  624. if(method_exists('File_thumbnail','url')) {
  625. $thumb_url = File_thumbnail::url($qthumb->filename);
  626. } else {
  627. $thumb_url = $qthumb->getUrl();
  628. }
  629. $attachment['quoted_notice']['attachments'][] = array('thumb_url'=>$thumb_url,
  630. 'attachment_id'=>$q_attach->id);
  631. } catch (Exception $e) {
  632. common_debug('Qvitter: could not get thumbnail for attachment id='.$q_attach->id.' in quoted notice id='.$quoted_notice->id);
  633. }
  634. }
  635. }
  636. }
  637. }
  638. }
  639. }
  640. }
  641. try {
  642. $twitter_status['external_url'] = $notice->getUrl(true);
  643. } catch (InvalidUrlException $e) {
  644. common_debug('Qvitter: No URL available for external notice: id='.$notice->id);
  645. }
  646. // reply-to profile url
  647. try {
  648. $reply = $notice->getParent();
  649. $reply_profile = $reply->getProfile();
  650. $twitter_status['in_reply_to_profileurl'] = $reply_profile->getUrl();
  651. $twitter_status['in_reply_to_ostatus_uri'] = $reply_profile->getUri();
  652. } catch (ServerException $e) {
  653. $twitter_status['in_reply_to_profileurl'] = null;
  654. $twitter_status['in_reply_to_ostatus_uri'] = null;
  655. }
  656. // attentions
  657. try {
  658. $attentions = $notice->getAttentionProfiles();
  659. $attentions_array = array();
  660. foreach ($attentions as $attn) {
  661. if(!$attn->isGroup()) {
  662. $attentions_array[] = array(
  663. 'id' => $attn->getID(),
  664. 'screen_name' => $attn->getNickname(),
  665. 'fullname' => $attn->getStreamName(),
  666. 'profileurl' => $attn->getUrl(),
  667. 'ostatus_uri' => $attn->getUri(),
  668. );
  669. }
  670. }
  671. $twitter_status['attentions'] = $attentions_array;
  672. } catch (Exception $e) {
  673. //
  674. }
  675. // fave number
  676. $faves = Fave::byNotice($notice);
  677. $favenum = count($faves);
  678. $twitter_status['fave_num'] = $favenum;
  679. // repeat number
  680. $repeats = $notice->repeatStream();
  681. $repeatnum=0;
  682. while ($repeats->fetch()) {
  683. if($repeats->verb == ActivityVerb::SHARE) { // i.e. not deleted repeats
  684. $repeatnum++;
  685. }
  686. }
  687. $twitter_status['repeat_num'] = $repeatnum;
  688. // is this a post? (previously is_activity)
  689. if(method_exists('ActivityUtils','compareVerbs')) {
  690. $twitter_status['is_post_verb'] = ActivityUtils::compareVerbs($notice->verb, array(ActivityVerb::POST));
  691. }
  692. else {
  693. $twitter_status['is_post_verb'] = ($notice->verb == ActivityVerb::POST ? true : false);
  694. }
  695. // some more metadata about notice
  696. if($notice->is_local == '1') {
  697. $twitter_status['is_local'] = true;
  698. }
  699. else {
  700. $twitter_status['is_local'] = false;
  701. if ($twitter_status['is_post_verb'] === true) {
  702. try {
  703. $twitter_status['external_url'] = $notice->getUrl(true);
  704. } catch (InvalidUrlException $e) {
  705. common_debug('Qvitter: No URL available for external notice: id='.$notice->id);
  706. }
  707. }
  708. }
  709. if(ActivityUtils::compareTypes($notice->verb, array('qvitter-delete-notice', 'delete'))) {
  710. $twitter_status['qvitter_delete_notice'] = true;
  711. }
  712. return true;
  713. }
  714. /**
  715. * Cover photo in API response, also follows_you, etc
  716. *
  717. * @return boolean hook return
  718. */
  719. function onTwitterUserArray($profile, &$twitter_user, $scoped): bool
  720. {
  721. $twitter_user['cover_photo'] = Profile_prefs::getConfigData($profile, 'qvitter', 'cover_photo');
  722. $twitter_user['background_image'] = Profile_prefs::getConfigData($profile, 'qvitter', 'background_image');
  723. // twitter compatible
  724. $twitter_user['profile_link_color'] = Profile_prefs::getConfigData($profile, 'theme', 'linkcolor');
  725. $twitter_user['profile_background_color'] = Profile_prefs::getConfigData($profile, 'theme', 'backgroundcolor');
  726. $twitter_user['profile_banner_url'] = Profile_prefs::getConfigData($profile, 'qvitter', 'cover_photo');
  727. if ($scoped) {
  728. // follows me?
  729. $twitter_user['follows_you'] = $profile->isSubscribed($scoped);
  730. // blocks me?
  731. $twitter_user['blocks_you'] = $profile->hasBlocked($scoped);
  732. }
  733. // local user?
  734. $twitter_user['is_local'] = $profile->isLocal();
  735. // silenced?
  736. $twitter_user['is_silenced'] = $profile->isSilenced();
  737. // rights
  738. $twitter_user['rights'] = array();
  739. $twitter_user['rights']['delete_user'] = $profile->hasRight(Right::DELETEUSER);
  740. $twitter_user['rights']['delete_others_notice'] = $profile->hasRight(Right::DELETEOTHERSNOTICE);
  741. $twitter_user['rights']['silence'] = $profile->hasRight(Right::SILENCEUSER);
  742. $twitter_user['rights']['sandbox'] = $profile->hasRight(Right::SANDBOXUSER);
  743. // sandboxed?
  744. $twitter_user['is_sandboxed'] = $profile->isSandboxed();
  745. // ostatus uri
  746. if($twitter_user['is_local']) {
  747. $user = $profile->getUser();
  748. if($user instanceof User) {
  749. $twitter_user['ostatus_uri'] = $user->uri;
  750. }
  751. } else {
  752. $ostatus_profile = Ostatus_profile::getKV('profile_id',$profile->id);
  753. if($ostatus_profile instanceof Ostatus_profile) {
  754. $twitter_user['ostatus_uri'] = $ostatus_profile->uri;
  755. }
  756. }
  757. return true;
  758. }
  759. /**
  760. * Insert into notification table
  761. */
  762. function insertNotification($to_profile_id, $from_profile_id, $ntype, $notice_id=false)
  763. {
  764. $to_user = User::getKV('id', $to_profile_id);
  765. $from_profile = Profile::getKV($from_profile_id);
  766. // don't notify remote profiles
  767. if (!$to_user instanceof User) {
  768. return false;
  769. }
  770. // no notifications from blocked profiles
  771. if ($to_user->hasBlocked($from_profile)) {
  772. return false;
  773. }
  774. // never notify myself
  775. if($to_profile_id == $from_profile_id) {
  776. return false;
  777. }
  778. // insert
  779. $notif = new QvitterNotification();
  780. $notif->to_profile_id = $to_profile_id;
  781. $notif->from_profile_id = $from_profile_id;
  782. $notif->ntype = $ntype;
  783. $notif->notice_id = $notice_id;
  784. $notif->created = common_sql_now();
  785. if (!$notif->insert()) {
  786. common_log_db_error($notif, 'INSERT', __FILE__);
  787. return false;
  788. }
  789. return true;
  790. }
  791. /**
  792. * Insert likes in notification table
  793. */
  794. public function onEndFavorNotice($profile, $notice)
  795. {
  796. // don't notify people favoriting their own notices
  797. if($notice->profile_id != $profile->id) {
  798. $this->insertNotification($notice->profile_id, $profile->id, 'like', $notice->id);
  799. }
  800. // mark reply and mention notifications as seen if i'm liking a notice i'm notified about
  801. self::markNotificationAsSeen($notice->id,$profile->id,'mention');
  802. self::markNotificationAsSeen($notice->id,$profile->id,'reply');
  803. }
  804. /**
  805. * Mark single notification as seen
  806. */
  807. public static function markNotificationAsSeen($notice_id, $to_profile_id, $ntype)
  808. {
  809. $notification_to_mark_as_seen = QvitterNotification::pkeyGet(array(
  810. 'is_seen' => 0,
  811. 'notice_id' => $notice_id,
  812. 'to_profile_id' => $to_profile_id,
  813. 'ntype' => $ntype
  814. ));
  815. if($notification_to_mark_as_seen instanceof QvitterNotification) {
  816. $orig = clone($notification_to_mark_as_seen);
  817. $notification_to_mark_as_seen->is_seen = 1;
  818. $notification_to_mark_as_seen->update($orig);
  819. }
  820. }
  821. /**
  822. * Remove likes in notification table on dislike
  823. */
  824. public function onEndDisfavorNotice($profile, $notice): bool
  825. {
  826. $notif = new QvitterNotification();
  827. $notif->from_profile_id = $profile->id;
  828. $notif->notice_id = $notice->id;
  829. $notif->ntype = 'like';
  830. $notif->delete();
  831. return true;
  832. }
  833. /**
  834. * Insert notifications for replies, mentions and repeats
  835. *
  836. * @return boolean hook flag
  837. */
  838. function onStartNoticeDistribute($notice): bool {
  839. assert($notice->id > 0); // since we removed tests below
  840. // repeats
  841. if ($notice->isRepeat()) {
  842. $repeated_notice = Notice::getKV('id', $notice->repeat_of);
  843. if ($repeated_notice instanceof Notice) {
  844. $this->insertNotification($repeated_notice->profile_id, $notice->profile_id, 'repeat', $repeated_notice->id);
  845. // mark reply/mention-notifications as read if we're repeating to a notice we're notified about
  846. self::markNotificationAsSeen($repeated_notice->id,$notice->profile_id,'mention');
  847. self::markNotificationAsSeen($repeated_notice->id,$notice->profile_id,'reply');
  848. // (no other notifications repeats)
  849. return true;
  850. }
  851. }
  852. // don't add notifications for activity/non-post-verb notices
  853. if(method_exists('ActivityUtils','compareVerbs')) {
  854. $is_post_verb = ActivityUtils::compareVerbs($notice->verb, array(ActivityVerb::POST));
  855. }
  856. else {
  857. $is_post_verb = ($notice->verb == ActivityVerb::POST ? true : false);
  858. }
  859. if($notice->source == 'activity' || !$is_post_verb) {
  860. return true;
  861. }
  862. // mark reply/mention-notifications as read if we're replying to a notice we're notified about
  863. if($notice->reply_to) {
  864. self::markNotificationAsSeen($notice->reply_to,$notice->profile_id,'mention');
  865. self::markNotificationAsSeen($notice->reply_to,$notice->profile_id,'reply');
  866. }
  867. // replies and mentions
  868. $reply_notification_to = false;
  869. // check for reply to insert in notifications
  870. if($notice->reply_to) {
  871. try {
  872. $replyauthor = $notice->getParent()->getProfile();
  873. $reply_notification_to = $replyauthor->id;
  874. $this->insertNotification($replyauthor->id, $notice->profile_id, 'reply', $notice->id);
  875. //} catch (NoParentNoticeException $e) { // TODO: catch this when everyone runs latest GNU social!
  876. // This is not a reply to something (has no parent)
  877. } catch (NoResultException $e) {
  878. // Parent author's profile not found! Complain louder?
  879. common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage());
  880. }
  881. }
  882. // check for mentions to insert in notifications
  883. $mentions = $notice->getReplies();
  884. $sender = Profile::getKV($notice->profile_id);
  885. $all_mentioned_user_ids = array();
  886. foreach ($mentions as $mentioned) {
  887. // no duplicate mentions
  888. if(in_array($mentioned, $all_mentioned_user_ids)) {
  889. continue;
  890. }
  891. $all_mentioned_user_ids[] = $mentioned;
  892. // only notify if mentioned user is not already notified for reply
  893. if($reply_notification_to != $mentioned) {
  894. $this->insertNotification($mentioned, $notice->profile_id, 'mention', $notice->id);
  895. }
  896. }
  897. return true;
  898. }
  899. /**
  900. * Delete any notifications tied to deleted notices and un-repeats
  901. *
  902. * @return boolean hook flag
  903. */
  904. public function onNoticeDeleteRelated($notice): bool
  905. {
  906. $notif = new QvitterNotification();
  907. // unrepeats
  908. if ($notice->isRepeat()) {
  909. $repeated_notice = Notice::getKV('id', $notice->repeat_of);
  910. $notif->notice_id = $repeated_notice->id;
  911. $notif->from_profile_id = $notice->profile_id;
  912. }
  913. // notices
  914. else {
  915. $notif->notice_id = $notice->id;
  916. }
  917. $notif->delete();
  918. // don't delete if this is a user is being deleted
  919. // because that creates an infinite loop of deleting and creating notices...
  920. $user_is_deleted = false;
  921. try {
  922. // outputs an activity notice that this notice was deleted
  923. $profile = $notice->getProfile();
  924. $user = User::getKV('id',$profile->id);
  925. if($user instanceof User && $user->hasRole(Profile_role::DELETED)) {
  926. $user_is_deleted = true;
  927. }
  928. } catch (NoProfileException $e) {
  929. $user_is_deleted = true;
  930. }
  931. if(!$user_is_deleted && class_exists('GNUsocial') && !array_key_exists('ActivityModeration', GNUsocial::getActiveModules())) {
  932. $rendered = sprintf(_m('<a href="%1$s">%2$s</a> deleted notice <a href="%3$s">{{%4$s}}</a>.'),
  933. htmlspecialchars($profile->getUrl()),
  934. htmlspecialchars($profile->getBestName()),
  935. htmlspecialchars($notice->getUrl()),
  936. htmlspecialchars($notice->uri));
  937. $text = sprintf(_m('%1$s deleted notice {{%2$s}}.'),
  938. $profile->getBestName(),
  939. $notice->uri);
  940. $uri = TagURI::mint('delete-notice:%d:%d:%s',
  941. $notice->profile_id,
  942. $notice->id,
  943. common_date_iso8601(common_sql_now()));
  944. $notice = Notice::saveNew($notice->profile_id,
  945. $text,
  946. ActivityPlugin::SOURCE,
  947. array('rendered' => $rendered,
  948. 'urls' => array(),
  949. 'uri' => $uri,
  950. 'verb' => 'qvitter-delete-notice',
  951. 'object_type' => ActivityObject::ACTIVITY));
  952. }
  953. return true;
  954. }
  955. /**
  956. * Checks for deleted remote notices and deleted the locally
  957. * A local qvitter-delete-notice is outputted in the onNoticeDeleteRelated event above
  958. *
  959. * @return boolean hook flag
  960. */
  961. public function onEndHandleFeedEntry($activity): bool {
  962. if($activity->verb == 'qvitter-delete-notice' && class_exists('GNUsocial') && !array_key_exists('ActivityModeration', GNUsocial::getActiveModules())) {
  963. $deleter_profile_uri = $activity->actor->id;
  964. $deleted_notice_uri = $activity->objects[0]->objects[0]->content;
  965. $deleted_notice_uri = substr($deleted_notice_uri,strpos($deleted_notice_uri,'{{')+2);
  966. $deleted_notice_uri = substr($deleted_notice_uri,0,strpos($deleted_notice_uri,'}}'));
  967. $deleter_ostatus_profile = Ostatus_profile::getKV('uri', $deleter_profile_uri);
  968. if(!$deleter_ostatus_profile instanceof Ostatus_profile) {
  969. return true;
  970. }
  971. $deleter_profile = Profile::getKV('id', $deleter_ostatus_profile->profile_id);
  972. $deleted_notice = Notice::getKV('uri', $deleted_notice_uri);
  973. if(!($deleter_profile instanceof Profile) || !($deleted_notice instanceof Notice)) {
  974. return true;
  975. }
  976. if($deleter_profile->id != $deleted_notice->profile_id) {
  977. return true;
  978. }
  979. $deleted_notice->delete();
  980. }
  981. return true;
  982. }
  983. /**
  984. * Add notification on subscription, remove on unsubscribe
  985. *
  986. * @return boolean hook flag
  987. */
  988. public function onEndSubscribe($subscriber, $other): bool
  989. {
  990. if(Subscription::exists($subscriber, $other)) {
  991. $this->insertNotification($other->id, $subscriber->id, 'follow', 1);
  992. }
  993. return true;
  994. }
  995. public function onEndUnsubscribe($subscriber, $other): bool
  996. {
  997. if(!Subscription::exists($subscriber, $other)) {
  998. $notif = new QvitterNotification();
  999. $notif->to_profile_id = $other->id;
  1000. $notif->from_profile_id = $subscriber->id;
  1001. $notif->ntype = 'follow';
  1002. $notif->delete();
  1003. }
  1004. return true;
  1005. }
  1006. /**
  1007. * Replace GNU Social's default FAQ with Qvitter's
  1008. *
  1009. * @return boolean hook flag
  1010. */
  1011. public function onEndLoadDoc($title, &$output): bool
  1012. {
  1013. if($title == 'faq') {
  1014. $faq = file_get_contents(QVITTERDIR.'/doc/en/faq.html');
  1015. $faq = str_replace('{instance-name}',common_config('site','name'),$faq);
  1016. $faq = str_replace('{instance-url}',common_config('site','server'),$faq);
  1017. $faq = str_replace('{instance-url-with-protocol}',common_path('', true),$faq);
  1018. if (common_logged_in()) {
  1019. $user = common_current_user();
  1020. $faq = str_replace('{nickname}',$user->nickname,$faq);
  1021. }
  1022. $output = $faq;
  1023. }
  1024. return true;
  1025. }
  1026. /**
  1027. * Add menu items to top header in Classic
  1028. *
  1029. * @return boolean hook flag
  1030. */
  1031. public function onStartPrimaryNav($action): bool
  1032. {
  1033. $action->menuItem(common_local_url('doc', array('title' => 'faq')),
  1034. // TRANS: Menu item in primary navigation panel.
  1035. _m('MENU','FAQ'),
  1036. // TRANS: Menu item title in primary navigation panel.
  1037. _('Frequently asked questions'),
  1038. false,
  1039. 'top_nav_doc_faq');
  1040. return true;
  1041. }
  1042. /**
  1043. * No registration for blocked ips
  1044. *
  1045. * @return boolean hook flag
  1046. */
  1047. public function onStartUserRegister($profile): bool
  1048. {
  1049. if(is_array(self::settings("blocked_ips"))) {
  1050. if(in_array($_SERVER['REMOTE_ADDR'], self::settings("blocked_ips"))) {
  1051. return false;
  1052. }
  1053. }
  1054. return true;
  1055. }
  1056. /**
  1057. * Correct group mentions
  1058. *
  1059. * We get the correct group ids in a $_POST var called "post_to_groups", formatted as a string with ids separated by colon, e.g. 4:5
  1060. *
  1061. * @return boolean hook flag
  1062. */
  1063. public function onEndFindMentions($sender, $text, &$mentions): bool {
  1064. // get the correct group profiles
  1065. if(isset($_POST['post_to_groups'])) {
  1066. $correct_group_mentions = explode(':',$_POST['post_to_groups']);
  1067. foreach($correct_group_mentions as $group_id) {
  1068. $correct_groups[] = User_group::getKV('id',$group_id);
  1069. }
  1070. // loop through the groups guessed by gnu social's common_find_mentions() and correct them
  1071. foreach($mentions as $mention_array_id=>$mention) {
  1072. foreach($correct_groups as $correct_groups_array_id=>$correct_group) {
  1073. if(isset($mention['mentioned'][0]->nickname)
  1074. && isset($correct_group->nickname)
  1075. && $mention['mentioned'][0]->nickname == $correct_group->nickname
  1076. && !isset($mentions[$mention_array_id]['corrected'])) {
  1077. $user_group_profile = Profile::getKV('id',$correct_group->profile_id);
  1078. $mentions[$mention_array_id]['mentioned'][0] = $user_group_profile;
  1079. $mentions[$mention_array_id]['url'] = $correct_group->permalink();
  1080. $mentions[$mention_array_id]['title'] = $correct_group->getFancyName();
  1081. $mentions[$mention_array_id]['corrected'] = true;
  1082. // now we've used this
  1083. unset($correct_groups[$correct_groups_array_id]);
  1084. }
  1085. }
  1086. }
  1087. }
  1088. return true;
  1089. }
  1090. /**
  1091. * Add unread notification count to all API responses, when logged in
  1092. *
  1093. * @return boolean hook flag
  1094. */
  1095. public function onEndSetApiUser($user): bool {
  1096. // cleanup sessions, to allow for simultaneous http-requests,
  1097. // e.g. if posting a notice takes a very long time
  1098. Session::cleanup();
  1099. if (!$user instanceof User) {
  1100. return true;
  1101. }
  1102. $user_id = $user->id;
  1103. $profile = $user->getProfile();
  1104. $notification = new QvitterNotification();
  1105. $notification->selectAdd();
  1106. $notification->selectAdd('ntype');
  1107. $notification->selectAdd('count(id) as count');
  1108. $notification->whereAdd("(to_profile_id = '".$user_id."')");
  1109. // if the user only want notifications from users they follow
  1110. $only_show_notifications_from_users_i_follow = Profile_prefs::getConfigData($profile, 'qvitter', 'only_show_notifications_from_users_i_follow');
  1111. if($only_show_notifications_from_users_i_follow == '1') {
  1112. $notification->whereAdd(sprintf('qvitternotification.from_profile_id IN (SELECT subscribed FROM subscription WHERE subscriber = %u)', $user_id));
  1113. }
  1114. // the user might have opted out from notifications from profiles they have muted
  1115. $hide_notifications_from_muted_users = Profile_prefs::getConfigData($profile, 'qvitter', 'hide_notifications_from_muted_users');
  1116. if($hide_notifications_from_muted_users == '1') {
  1117. $muted_ids = QvitterMuted::getMutedIDs($profile->id,0,10000); // get all (hopefully not more than 10 000...)
  1118. if($muted_ids !== false && count($muted_ids) > 0) {
  1119. $ids_imploded = implode(',',$muted_ids);
  1120. $notification->whereAdd('qvitternotification.from_profile_id NOT IN ('.$ids_imploded.')');
  1121. }
  1122. }
  1123. // the user might have opted out from certain notification types
  1124. $current_profile = $user->getProfile();
  1125. $disable_notify_replies_and_mentions = Profile_prefs::getConfigData($current_profile, 'qvitter', 'disable_notify_replies_and_mentions');
  1126. $disable_notify_favs = Profile_prefs::getConfigData($current_profile, 'qvitter', 'disable_notify_favs');
  1127. $disable_notify_repeats = Profile_prefs::getConfigData($current_profile, 'qvitter', 'disable_notify_repeats');
  1128. $disable_notify_follows = Profile_prefs::getConfigData($current_profile, 'qvitter', 'disable_notify_follows');
  1129. if($disable_notify_replies_and_mentions == '1') {
  1130. $notification->whereAdd('qvitternotification.ntype != "mention"');
  1131. $notification->whereAdd('qvitternotification.ntype != "reply"');
  1132. }
  1133. if($disable_notify_favs == '1') {
  1134. $notification->whereAdd('qvitternotification.ntype != "like"');
  1135. }
  1136. if($disable_notify_repeats == '1') {
  1137. $notification->whereAdd('qvitternotification.ntype != "repeat"');
  1138. }
  1139. if($disable_notify_follows == '1') {
  1140. $notification->whereAdd('qvitternotification.ntype != "follow"');
  1141. }
  1142. $notification->groupBy('ntype');
  1143. $notification->whereAdd("(is_seen = '0')");
  1144. $notification->whereAdd("(notice_id IS NOT NULL)"); // sometimes notice_id is NULL, those notifications are corrupt and should be discarded
  1145. $notification->find();
  1146. $new_notifications = array();
  1147. while ($notification->fetch()) {
  1148. $new_notifications[$notification->ntype] = $notification->count;
  1149. }
  1150. header('Qvitter-Notifications: '.json_encode($new_notifications));
  1151. return true;
  1152. }
  1153. function onPluginVersion(array &$versions): bool
  1154. {
  1155. $versions[] = array('name' => 'Qvitter',
  1156. 'version' => '5-alpha',
  1157. 'author' => 'Hannes Mannerheim',
  1158. 'homepage' => 'https://git.gnu.io/h2p/Qvitter',
  1159. 'rawdescription' => _m('User interface'));
  1160. return true;
  1161. }
  1162. function qvitterTwitterUserArray($profile)
  1163. {
  1164. $twitter_user = array();
  1165. try {
  1166. $user = $profile->getUser();
  1167. } catch (NoSuchUserException $e) {
  1168. $user = null;
  1169. }
  1170. $twitter_user['id'] = intval($profile->id);
  1171. $twitter_user['name'] = $profile->getBestName();
  1172. $twitter_user['screen_name'] = $profile->nickname;
  1173. $twitter_user['location'] = ($profile->location) ? $profile->location : null;
  1174. $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
  1175. // TODO: avatar url template (example.com/user/avatar?size={x}x{y})
  1176. $twitter_user['profile_image_url'] = Avatar::urlByProfile($profile, AVATAR_STREAM_SIZE);
  1177. $twitter_user['profile_image_url_https'] = $twitter_user['profile_image_url'];
  1178. // START introduced by qvitter API, not necessary for StatusNet API
  1179. $twitter_user['profile_image_url_profile_size'] = Avatar::urlByProfile($profile, AVATAR_PROFILE_SIZE);
  1180. try {
  1181. $avatar = Avatar::getUploaded($profile);
  1182. $origurl = $avatar->displayUrl();
  1183. } catch (Exception $e) {
  1184. // ugly fix if avatar is missing in the db but exists on the server
  1185. $largest_avatar = array('name'=>false,'size'=>0);
  1186. foreach (glob('avatar/'.$profile->id.'-*') as $filename) {
  1187. $size = filesize($filename);
  1188. if($size > $largest_avatar['size']) {
  1189. $largest_avatar['size'] = $size;
  1190. $largest_avatar['name'] = $filename;
  1191. }
  1192. }
  1193. if($largest_avatar['size']>0) {
  1194. $origurl = common_path('', GNUsocial::isHTTPS()).$largest_avatar['name'];
  1195. } else {
  1196. $origurl = $twitter_user['profile_image_url_profile_size'];
  1197. }
  1198. }
  1199. $twitter_user['profile_image_url_original'] = $origurl;
  1200. $twitter_user['groups_count'] = $profile->getGroupCount();
  1201. foreach (array('linkcolor', 'backgroundcolor') as $key) {
  1202. $twitter_user[$key] = Profile_prefs::getConfigData($profile, 'theme', $key);
  1203. }
  1204. // END introduced by qvitter API, not necessary for StatusNet API
  1205. $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
  1206. $twitter_user['protected'] = (!empty($user) && $user->private_stream) ? true : false;
  1207. $twitter_user['followers_count'] = $profile->subscriberCount();
  1208. // Note: some profiles don't have an associated user
  1209. $twitter_user['friends_count'] = $profile->subscriptionCount();
  1210. $twitter_user['created_at'] = ApiAction::dateTwitter($profile->created);
  1211. $timezone = 'UTC';
  1212. if (!empty($user) && $user->timezone) {
  1213. $timezone = $user->timezone;
  1214. }
  1215. $t = new DateTime;
  1216. $t->setTimezone(new DateTimeZone($timezone));
  1217. $twitter_user['utc_offset'] = $t->format('Z');
  1218. $twitter_user['time_zone'] = $timezone;
  1219. $twitter_user['statuses_count'] = $profile->noticeCount();
  1220. // Is the requesting user following this user?
  1221. $twitter_user['following'] = false;
  1222. $twitter_user['statusnet_blocking'] = false;
  1223. $logged_in_profile = null;
  1224. if(common_logged_in()) {
  1225. $logged_in_profile = Profile::current();
  1226. $twitter_user['following'] = $logged_in_profile->isSubscribed($profile);
  1227. $twitter_user['statusnet_blocking'] = $logged_in_profile->hasBlocked($profile);
  1228. }
  1229. // StatusNet-specific
  1230. $twitter_user['statusnet_profile_url'] = $profile->profileurl;
  1231. Event::handle('TwitterUserArray', array($profile, &$twitter_user, $logged_in_profile, array()));
  1232. return $twitter_user;
  1233. }
  1234. }
  1235. /**
  1236. * Overwrites variables in URL-mapping
  1237. *
  1238. */
  1239. class URLMapperOverwrite extends URLMapper
  1240. {
  1241. static function overwrite_variable($m, $path, $args, $paramPatterns, $newaction)
  1242. {
  1243. $m->connect($path, array('action' => $newaction), $paramPatterns);
  1244. $regex = self::makeRegex($path, $paramPatterns);
  1245. foreach($m->variables as $n=>$v)
  1246. if($v[1] == $regex)
  1247. $m->variables[$n][0]['action'] = $newaction;
  1248. }
  1249. }