QvitterPlugin.php 61 KB

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