flag_lists.module 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826
  1. <?php
  2. module_load_include('inc', 'flag', 'includes/flag.admin');
  3. module_load_include('inc', 'flag', 'flag');
  4. /**
  5. * @file
  6. * The Flag Lists module.
  7. *
  8. * Extends flag to allow individual users to create personal flags.
  9. */
  10. /**
  11. * Implementation of hook_menu().
  12. */
  13. function flag_lists_menu() {
  14. $items = array();
  15. $items[FLAG_ADMIN_PATH . '/lists'] = array(
  16. 'title' => 'Lists',
  17. 'page callback' => 'drupal_get_form',
  18. 'page arguments' => array('flag_lists_settings_form'),
  19. 'access callback' => 'user_access',
  20. 'access arguments' => array('administer flags'),
  21. 'description' => 'Configure default settings allowing users to mark content with personal flags.',
  22. 'file' => 'flag_lists.admin.inc',
  23. 'type' => MENU_LOCAL_TASK,
  24. 'weight' => 100,
  25. );
  26. $items[FLAG_ADMIN_PATH . '/lists/settings'] = array(
  27. 'title' => 'Settings',
  28. 'page callback' => 'drupal_get_form',
  29. 'page arguments' => array('flag_lists_settings_form'),
  30. 'access callback' => 'user_access',
  31. 'access arguments' => array('administer flags'),
  32. 'description' => 'Configure default settings allowing users to mark content with personal flags.',
  33. 'file' => 'flag_lists.admin.inc',
  34. 'type' => MENU_DEFAULT_LOCAL_TASK,
  35. 'weight' => 1,
  36. );
  37. $items[FLAG_ADMIN_PATH . '/lists/list'] = array(
  38. 'title' => 'List',
  39. 'page callback' => 'flag_lists_admin_page',
  40. 'access callback' => 'user_access',
  41. 'access arguments' => array('administer flags'),
  42. 'file' => 'flag_lists.admin.inc',
  43. 'type' => MENU_LOCAL_TASK,
  44. 'weight' => 2,
  45. );
  46. $items[FLAG_ADMIN_PATH . '/lists/template'] = array(
  47. 'title' => 'New template',
  48. 'page callback' => 'drupal_get_form',
  49. 'page arguments' => array('flag_lists_create_template_form'),
  50. 'access callback' => 'user_access',
  51. 'access arguments' => array('administer flags'),
  52. 'file' => 'flag_lists.admin.inc',
  53. 'type' => MENU_LOCAL_TASK,
  54. 'weight' => 3,
  55. );
  56. if (module_exists('devel_generate')) {
  57. $items['admin/config/development/generate/flag-lists'] = array(
  58. 'title' => 'Generate lists',
  59. 'description' => 'Generate a given number of lists and listings on site content. Optionally delete existing lists and listings.',
  60. 'page callback' => 'drupal_get_form',
  61. 'page arguments' => array('flag_lists_generate_lists_form'),
  62. 'access callback' => 'user_access',
  63. 'access arguments' => array('administer flags'),
  64. 'file' => 'flag_lists.admin.inc',
  65. );
  66. }
  67. $items['flag-lists/add/%'] = array(
  68. 'title' => 'Add a list',
  69. 'page callback' => 'flag_lists_add',
  70. 'page arguments' => array(2),
  71. 'access callback' => 'user_access',
  72. 'access arguments' => array('create flag lists'),
  73. 'file' => 'flag_lists.admin.inc',
  74. 'type' => MENU_NORMAL_ITEM,
  75. );
  76. // Callback for adding a new list through JS
  77. $items['flag-lists/add/%/js'] = array(
  78. 'title' => 'Add a list',
  79. 'page callback' => 'flag_lists_add_js',
  80. 'page arguments' => array(2),
  81. 'access callback' => 'user_access',
  82. 'access arguments' => array('create flag lists'),
  83. 'file' => 'flag_lists.admin.inc',
  84. 'type' => MENU_CALLBACK,
  85. );
  86. // Callback for autocomplete of lists in views filter
  87. $items['flag-lists/autocomplete_list_callback'] = array(
  88. 'title' => 'Find a list',
  89. 'page callback' => 'flag_lists_autocomplete_list_callback',
  90. 'access callback' => 'user_access',
  91. 'access arguments' => array('create flag lists'),
  92. 'file' => 'flag_lists.admin.inc',
  93. 'type' => MENU_CALLBACK,
  94. );
  95. $items['flag/lists/edit/%'] = array(
  96. 'title' => 'Edit a list',
  97. 'page callback' => 'drupal_get_form',
  98. 'page arguments' => array('flag_lists_form', 3),
  99. 'access callback' => 'flag_lists_is_owner',
  100. 'access arguments' => array(2, 3),
  101. 'file' => 'flag_lists.admin.inc',
  102. 'type' => MENU_CALLBACK,
  103. );
  104. $items['flag/lists/delete/%'] = array(
  105. 'title' => 'Delete a list',
  106. 'page callback' => 'drupal_get_form',
  107. 'page arguments' => array('flag_lists_delete_confirm', 3),
  108. 'access callback' => 'flag_lists_is_owner',
  109. 'access arguments' => array(2, 3),
  110. 'file' => 'flag_lists.admin.inc',
  111. 'type' => MENU_CALLBACK,
  112. );
  113. $items[FLAG_ADMIN_PATH . '/flag-lists/rebuild'] = array(
  114. 'title' => 'Rebuild all flag lists',
  115. 'page callback' => 'drupal_get_form',
  116. 'page arguments' => array('flag_lists_rebuild_confirm'),
  117. 'access callback' => 'user_access',
  118. 'access arguments' => array('administer flag lists'),
  119. 'file' => 'flag_lists.admin.inc',
  120. 'type' => MENU_CALLBACK,
  121. );
  122. $items['flag-lists'] = array(
  123. 'title' => 'Flag',
  124. 'page callback' => 'flag_lists_page',
  125. 'access callback' => 'user_access',
  126. 'access arguments' => array('access content'),
  127. 'type' => MENU_CALLBACK,
  128. );
  129. $items['user/%user/flag/lists'] = array(
  130. 'title' =>drupal_ucfirst(variable_get('flag_lists_name', 'list')),
  131. 'page callback' => 'flag_lists_user_page',
  132. 'page arguments' => array(1),
  133. 'access callback' => 'user_access',
  134. 'access arguments' => array('view flag lists'),
  135. 'type' => MENU_LOCAL_TASK,
  136. );
  137. $items['user/%user/flag/lists/%'] = array(
  138. 'title' =>drupal_ucfirst(variable_get('flag_lists_name', 'list')
  139. . ' ' . 'content'),
  140. 'page callback' => 'flag_lists_user_list',
  141. 'page arguments' => array(1, 4),
  142. 'access callback' => 'user_access',
  143. 'access arguments' => array('view flag lists'),
  144. 'type' => MENU_CALLBACK,
  145. );
  146. return $items;
  147. }
  148. /**
  149. * User flag page. Display a list of user-created flag lists.
  150. */
  151. function flag_lists_user_page($user) {
  152. // Can we use our default view?
  153. if (module_exists('views')) {
  154. $view = views_get_view('flag_lists_user_lists', FALSE);
  155. if (!empty($view)) {
  156. $view->set_display('default');
  157. $view->set_arguments(array($user->uid));
  158. $view->pre_execute();
  159. $output = $view->render();
  160. /* drupal_set_title(str_replace(array_keys($view->build_info['substitutions']), $view->build_info['substitutions'], $view->build_info['title'])); */
  161. }
  162. return $output;
  163. }
  164. else {
  165. return theme('flag_lists_user_page', array('uid' => $user->uid));
  166. }
  167. }
  168. /**
  169. * Theme the output for a user flag administration page.
  170. */
  171. function theme_flag_lists_user_page($variables) {
  172. $uid = $variables['uid'];
  173. $account = user_load($uid);
  174. drupal_set_title(t('Lists'));
  175. if ($flags = flag_lists_get_user_flags(NULL, $account)) {
  176. // Build the list of flag lists for this node.
  177. foreach ($flags as $flag) {
  178. $ops = theme('flag_lists_ops', array('flag' => $flag));
  179. $items[] = l($flag->title, "user/$uid/flag/lists/" . $flag->fid) . $ops;
  180. }
  181. }
  182. drupal_add_css(drupal_get_path('module', 'flag_lists') . '/theme/flag_lists.css');
  183. return theme('item_list', array('items' => $items));
  184. }
  185. /**
  186. * List the contents of a user-defined list
  187. */
  188. function flag_lists_user_list($user, $fid) {
  189. $uid = $user->uid;
  190. // Can we use our default view?
  191. if (module_exists('views')) {
  192. $view = views_get_view('flag_lists_user_list', FALSE);
  193. if (!empty($view)) {
  194. $view->set_display('default');
  195. $view->set_arguments(array($fid));
  196. $view->pre_execute();
  197. $output = $view->render();
  198. drupal_set_title(str_replace(array_keys($view->build_info['substitutions']), $view->build_info['substitutions'], $view->build_info['title']), PASS_THROUGH);
  199. }
  200. return $output;
  201. }
  202. else {
  203. return theme('flag_lists_user_list', array('uid' => $uid, 'fid' => $fid));
  204. }
  205. }
  206. /**
  207. * Theme the output of user-defined list page
  208. */
  209. function theme_flag_lists_user_list($variables) {
  210. $uid = $variables['uid'];
  211. $fid = $variables['fid'];
  212. $flag = flag_lists_get_flag($fid);
  213. drupal_set_title($flag->title);
  214. $content = flag_lists_get_flagged_content($fid, $uid);
  215. foreach ($content as $item) {
  216. if ($item->entity_type == 'node') {
  217. $node = node_load($item->entity_id);
  218. $items[] = l($node->title, 'node/' . $node->nid);
  219. }
  220. }
  221. $breadcrumb = menu_get_active_breadcrumb();
  222. $breadcrumb[] = l(t('@name lists', array('@name' => drupal_ucfirst(variable_get('flag_lists_name', 'list')))), 'user/' . arg(1) . '/flag/lists');
  223. drupal_set_breadcrumb($breadcrumb);
  224. return theme('item_list', array('items' => $items));
  225. }
  226. /**
  227. * Implementation of hook_theme().
  228. */
  229. function flag_lists_theme() {
  230. $path = drupal_get_path('module', 'flag') . '/theme';
  231. return array(
  232. 'flag_lists_list' => array(
  233. 'variables' => array('node' => NULL, 'create' => NULL, 'ops' => NULL, 'use_flags' => NULL),
  234. ),
  235. 'flag_lists_admin_page' => array(
  236. 'variables' => array('flags' => NULL),
  237. ),
  238. 'flag_lists_user_page' => array(
  239. 'variables' => array('uid' => NULL),
  240. ),
  241. 'flag_lists_user_list' => array(
  242. 'variables' => array('flag_name' => NULL),
  243. ),
  244. 'flag_lists_ops' => array(
  245. 'variables' => array('flag' => NULL),
  246. )
  247. );
  248. }
  249. /**
  250. * Implementation of hook_permission().
  251. */
  252. function flag_lists_permission() {
  253. return array(
  254. 'create flag lists' => array(
  255. 'title' => t('Create flag lists'),
  256. 'description' => t(''),
  257. ),
  258. 'edit own flag lists' => array(
  259. 'title' => t('Edit own flag lists'),
  260. 'description' => t(''),
  261. ),
  262. 'delete own flag lists' => array(
  263. 'title' => t('Delete own flag lists'),
  264. 'description' => t(''),
  265. ),
  266. 'view flag lists' => array(
  267. 'title' => t('View flag lists'),
  268. 'description' => t(''),
  269. )
  270. );
  271. }
  272. /**
  273. * Implementation of hook_form_alter().
  274. */
  275. function flag_lists_form_alter(&$form, &$form_state, $form_id) {
  276. switch ($form_id) {
  277. case 'flag_form':
  278. // A template flag should always have a record in the flag_lists_types table.
  279. $result = db_select('flag_lists_types', 'f')->fields('f')->execute();
  280. foreach ($result as $type) {
  281. $types[$type->name] = $type->type;
  282. }
  283. if (isset($types[$form['name']['#default_value']])) {
  284. $form['name']['#type'] = 'value';
  285. $form['global']['#type'] = 'value';
  286. $form['title']['#description'] = t('A short, descriptive title for this template. It will be used in administrative interfaces to refer to this template.');
  287. // Warn about types that already have a template.
  288. foreach ($form['access']['types']['#options'] as $option => $value) {
  289. if (in_array($option, $types) && $form['access']['types']['#default_value'] != $option) {
  290. $form['access']['types']['#options'][$option] .= '<span class="description">' . t('(Already has a template.)') . '</span>';
  291. }
  292. }
  293. $form['access']['types']['#description'] .= t('A type may only be selected in one list template.');
  294. // Unset anon permissions for now. @todo allow anon listing.
  295. unset($form['access']['roles']['flag']['#options'][1]);
  296. unset($form['access']['roles']['unflag']['#options'][1]);
  297. foreach (element_children($form['display']) as $display) {
  298. $form['display'][$display]['#type'] = 'value';
  299. }
  300. $form['display']['link_type']['#default_value'] = 'fl_template';
  301. $form['display']['#description'] = t('Unlike normal flags, lists are only displayed in a block provided by this module or in views blocks. See <a href="/admin/structure/block">the block admin page</a> to place the block.');
  302. unset($form['display']['link_options_confirm']['flag_confirmation']);
  303. unset($form['display']['link_options_confirm']['unflag_confirmation']);
  304. $form['display']['link_options_intro']['#children'] = '';
  305. $form['#validate'][] = 'flag_lists_template_validate';
  306. $form['#submit'][] = 'flag_lists_template_submit';
  307. }
  308. break;
  309. case 'views_exposed_form':
  310. // Force the exposed filters to perform actions on the page itself because
  311. // the views default viwe didn't come with a page display
  312. if ($form['#id'] == 'views-exposed-form-flag-lists-default') {
  313. $form['#action'] = '/' . implode('/', arg());
  314. }
  315. break;
  316. }
  317. // Flag lists operations related changes
  318. if (strpos($form_id, 'views_form_') === 0) {
  319. $flo = _flag_lists_ops_get_field($form_state['build_info']['args'][0]);
  320. // Not a FLO-enabled views form.
  321. if (empty($flo)) {
  322. return;
  323. }
  324. // Add FLO's custom callbacks.
  325. $form['#validate'][] = 'flag_lists_ops_form_validate';
  326. $form['#submit'][] = 'flag_lists_ops_form_submit';
  327. // Allow FLO to work when embedded using views_embed_view(), or in a block.
  328. if (empty($flo->view->override_path)) {
  329. if (!empty($flo->view->preview) || $flo->view->display_handler instanceof views_plugin_display_block) {
  330. $flo->view->override_path = $_GET['q'];
  331. }
  332. }
  333. // Quickfix for FLO & exposed filters using ajax. See http://drupal.org/node/1191928.
  334. $query = drupal_get_query_parameters($_GET, array('q'));
  335. $form['#action'] = url($flo->view->get_url(), array('query' => $query));
  336. // Add basic FLO functionality.
  337. if ($form_state['step'] == 'views_form_views_form') {
  338. $form = flag_lists_ops_form($form, $form_state, $flo);
  339. }
  340. }
  341. }
  342. /**
  343. * Add the Flag list select menu widget.
  344. */
  345. function flag_lists_ops_form($form, &$form_state, $flo) {
  346. $form['#attached']['js'][] = drupal_get_path('module', 'flag_lists') . '/js/flag_lists_ops.js';
  347. $form['#attached']['css'][] = drupal_get_path('module', 'flag_lists') . '/css/flag_lists_ops.css';
  348. $form['#prefix'] = '<div class="flo-views-form">';
  349. $form['#suffix'] = '</div>';
  350. $form_state['flo_operation'] = $flo->options['flo']['operation'];
  351. // Force browser to reload the page if Back is hit.
  352. if (preg_match('/msie/i', $_SERVER['HTTP_USER_AGENT'])) {
  353. drupal_add_http_header('Cache-Control', 'no-cache'); // works for IE6+
  354. }
  355. else {
  356. drupal_add_http_header('Cache-Control', 'no-store'); // works for Firefox and other browsers
  357. }
  358. global $user;
  359. global $base_url;
  360. $account = user_load($user->uid);
  361. $items = array();
  362. if ($flags = flag_lists_get_user_flags(NULL, $account)) {
  363. // Build the list of flag lists for this node.
  364. foreach ($flags as $flag) {
  365. $items[((string) $flag->fid)] = $flag->title;
  366. }
  367. }
  368. // Group items into a fieldset for easier theming.
  369. $form['flag_lists_' . $form_state['flo_operation']] = array(
  370. '#type' => 'fieldset',
  371. '#title' => t('@name operations', array( '@name' => drupal_ucfirst( variable_get('flag_lists_name', 'list')))),
  372. '#tree' => TRUE,
  373. '#attributes' => array('class' => array('flag-lists-ops-fieldset')),
  374. );
  375. switch ($flo->options['flo']['operation']) {
  376. case 'unflag':
  377. $null_text = t('Remove from a @name', array('@name' => variable_get('flag_lists_name', 'list')));
  378. $operation = 'unflag';
  379. $form['flag_lists_' . $form_state['flo_operation']]['list'] = array(
  380. '#type' => 'button',
  381. '#value' => t('Remove from @name', array('@name' => variable_get('flag_lists_name', 'list'))),
  382. '#ajax' => array(
  383. 'callback' => 'flag_lists_ops_form_ajax_callback',
  384. 'wrapper' => 'flag-list-ops-container-' . $flo->options['flo']['operation'],
  385. ),
  386. );
  387. break;
  388. default:
  389. $null_text = t('Add to a @name', array('@name' => variable_get('flag_lists_name', 'list')));
  390. $operation = 'flag';
  391. drupal_add_library('system', 'ui.dialog');
  392. $form['flag_lists_' . $form_state['flo_operation']]['list'] = array(
  393. '#type' => 'select',
  394. '#options' => array('0' => t('- ' . $null_text . ' -')) + $items,
  395. '#default_value' => '0',
  396. '#ajax' => array(
  397. 'callback' => 'flag_lists_ops_form_ajax_callback',
  398. 'wrapper' => 'flag-list-ops-container-' . $flo->options['flo']['operation'],
  399. ),
  400. '#attributes' => array(
  401. 'class' => array(
  402. 'flag-lists-ops-dropdown',
  403. ),
  404. ),
  405. );
  406. // Add the necessary JS for creating a new list via AJAX
  407. $result = db_select('flag_lists_types')
  408. ->fields('flag_lists_types')
  409. ->execute();
  410. $list_types = array();
  411. foreach ($result as $row) {
  412. if (!empty($row->type)) {
  413. $list_types[] = $row->type;
  414. }
  415. }
  416. drupal_add_js(array('flag_lists' => array('types' => $list_types, 'json_path' => $base_url . '/flag-lists/add/%/js', 'listname' => variable_get('flag_lists_name', 'list'), 'form_token' => drupal_get_token(variable_get('flag_lists_name','list')))), 'setting');
  417. break;
  418. }
  419. // Get the $ops from the originating form.
  420. if (!empty($form_state['values']['flag_lists_' . $form_state['flo_operation']]['list'])) {
  421. $list = $form_state['values']['flag_lists_' . $form_state['flo_operation']]['list'];
  422. }
  423. if (!empty($_REQUEST['flag_lists_ops'])) {
  424. $ops = $_REQUEST['flag_lists_ops'];
  425. $ops = is_array($ops) ? $ops : array($ops);
  426. }
  427. if (!empty($ops) && !empty($list) && $form_state['values']['flag_lists_' . $form_state['flo_operation']]['operation'] == 'unflag') {
  428. $hidden_deleted_values = '';
  429. foreach ($ops as $nid) {
  430. $hidden_deleted_values .= '<input type="hidden" class="flo-deleted-value" value="' . $nid . '" />';
  431. }
  432. }
  433. $form['flag_lists_' . $form_state['flo_operation']]['operation'] = array(
  434. '#type' => 'hidden',
  435. '#value' => $operation,
  436. );
  437. $form['flag_lists_' . $form_state['flo_operation']]['go'] = array(
  438. '#type' => 'submit',
  439. '#value' => t('Go'),
  440. '#attributes' => array(
  441. 'class' => array('flag-lists-ops-go'),
  442. ),
  443. );
  444. unset($form['actions']['submit']);
  445. // Generate a status message for AJAX submission.
  446. $form['flag_lists_' . $form_state['flo_operation']]['status_message'] = array('#markup' => '');
  447. $form['flag_lists_' . $form_state['flo_operation']]['#prefix'] = '<div id="flag-list-ops-container-' . $flo->options['flo']['operation'] . '">';
  448. $form['flag_lists_' . $form_state['flo_operation']]['#suffix'] = (!empty($hidden_deleted_values)) ? $hidden_deleted_values : '' . '</div>';
  449. return $form;
  450. }
  451. function flag_lists_ops_form_ajax_callback(&$form, &$form_state) {
  452. // The form has already been submitted and updated. We can return the replaced
  453. // item as it is.
  454. $message = '';
  455. if (!flag_lists_ops_form_validate($form, $form_state)) {
  456. $message = flag_lists_ops_form_submit($form, $form_state);
  457. }
  458. $form['flag_lists_' . $form_state['flo_operation']]['status_message']['#markup'] = '<div class="alert alert-success">' . $message . '</div>';
  459. if ($form_state['flo_operation'] == 'flag') {
  460. $form['flag_lists_' . $form_state['flo_operation']]['list']['#value'] = '0';
  461. }
  462. $form['flag_lists_' . $form_state['flo_operation']]['status_message']['#attributes']['class'][] = 'alert-success';
  463. return $form['flag_lists_' . $form_state['flo_operation']];
  464. }
  465. function flag_lists_ops_form_validate($form, &$form_state) {
  466. global $user;
  467. $error_count = 0;
  468. // Check to see if an items are selected, if not fail right away.
  469. if (!isset($_REQUEST['flag_lists_ops']) || empty($_REQUEST['flag_lists_ops'])) {
  470. form_set_error('', t('No content selected.'));
  471. $error_count++;
  472. return $error_count;
  473. }
  474. switch ($form_state['values']['flag_lists_' . $form_state['flo_operation']]['operation']) {
  475. case 'unflag':
  476. $user_flag_lists = flag_lists_get_user_flags();
  477. foreach ($user_flag_lists as $key => $op) {
  478. $ops[$key] = explode('_', $key);
  479. if (empty($ops[$key][3]) || !($flag = flag_lists_get_flag($ops[$key][3]))) {
  480. form_set_error('flag_lists][remove', t('Invalid options list selected to remove from.'));
  481. $error_count++;
  482. }
  483. else {
  484. if ($flag->uid != $user->uid) {
  485. form_set_error('flag_lists][remove', t('You are only allowed to remove content from your own lists.'));
  486. $error_count++;
  487. }
  488. }
  489. }
  490. break;
  491. default:
  492. if (empty($form_state['values']['flag_lists_' . $form_state['flo_operation']]['list'])) {
  493. form_set_error('flag_lists][list', t('No list selected. Please select a list to add to.'));
  494. $error_count++;
  495. }
  496. else {
  497. if (!($flag = flag_lists_get_flag($form_state['values']['flag_lists_' . $form_state['flo_operation']]['list']))) {
  498. form_set_error('flag_lists][list', t('Invalid list selected. Please select a list to add to.'));
  499. $error_count++;
  500. }
  501. else {
  502. if ($flag->uid != $user->uid) {
  503. form_set_error('flag_lists][list', t('Invalid list selected. Please select a list to add to.'));
  504. $error_count++;
  505. }
  506. }
  507. }
  508. break;
  509. }
  510. return $error_count;
  511. }
  512. function flag_lists_ops_form_submit($form, &$form_state) {
  513. // Get the $ops from the originating form.
  514. $ops = $_REQUEST['flag_lists_ops'];
  515. $ops = is_array($ops) ? $ops : array($ops);
  516. $success_count = 0;
  517. // Get the operation, or set it
  518. switch ($form_state['values']['flag_lists_' . $form_state['flo_operation']]['operation']) {
  519. case 'unflag':
  520. $operation = 'unflag';
  521. $message = 'removed from';
  522. $user_flag_lists = flag_lists_get_user_flags();
  523. foreach ($user_flag_lists as $key => $op) {
  524. // Iterate over all flags by this user
  525. list($k1,$k2,$owner,$ofid) = explode('_', $key);
  526. foreach ($ops as $op) {
  527. // Iterate over all selected items
  528. list($nid,$fid) = array_merge( explode('-', $op), array(FALSE));
  529. if ($fid <> 0 && $fid == $ofid) {
  530. if (($flag = flag_lists_get_flag($fid)) && ($node = node_load($nid))) {
  531. if (flag_lists_do_flag($flag, $operation, $nid)) {
  532. $success_count++;
  533. }
  534. }
  535. }
  536. }
  537. }
  538. break;
  539. default:
  540. $operation = 'flag';
  541. $message = 'added to';
  542. if ($flag = flag_lists_get_flag($form_state['values']['flag_lists_' . $form_state['flo_operation']]['list'])) {
  543. foreach ($ops as $nid) {
  544. if (flag_lists_do_flag($flag, $operation, $nid)) {
  545. $success_count++;
  546. }
  547. }
  548. }
  549. break;
  550. }
  551. if (!empty($flag->title) && $success_count <> 0) {
  552. $message = t('@count item(s) ' . $message . ' !listname !title', array('@count' => $success_count, '!listname' => variable_get('flag_lists_name','list'), '!title' => $flag->title));
  553. }
  554. else {
  555. $message = t('No items affected.');
  556. }
  557. if ($_GET['q'] != 'system/ajax') {
  558. drupal_set_message($message);
  559. }
  560. else {
  561. return $message;
  562. }
  563. }
  564. /**
  565. * Gets the FLO field if it exists on the passed-in view.
  566. *
  567. * @return
  568. * The field object if found. Otherwise, FALSE.
  569. */
  570. function _flag_lists_ops_get_field($view) {
  571. foreach ($view->field as $field_name => $field) {
  572. if ($field instanceof flag_lists_handler_field_ops) {
  573. // Add in the view object for convenience.
  574. $field->view = $view;
  575. return $field;
  576. }
  577. }
  578. return FALSE;
  579. }
  580. function flag_lists_template_validate($form, &$form_state) {
  581. $types = array_filter($form_state['values']['types']);
  582. $errors = array();
  583. foreach ($types as $type) {
  584. $result = db_select('flag_lists_types', 'f')
  585. ->fields('f')
  586. ->condition('type', $type)
  587. ->condition('name', $form_state['values']['name'], '<>')
  588. ->execute();
  589. foreach ($result as $errors) {
  590. $content_types[] = $errors->type;
  591. $templates[] = $errors->name;
  592. }
  593. }
  594. if (isset($content_types) && count($content_types)) {
  595. $content_types = implode(', ', $content_types);
  596. $templates = implode(', ', array_unique($templates));
  597. form_set_error('types', t('The flaggable content type(s) "@type" is(are) already assigned to the template(s) "@template." A content type may be assigned to only one template. To reassign a content type you must first remove its other assignment.', array(
  598. '@type' => $content_types,
  599. '@template' => $templates
  600. )));
  601. }
  602. }
  603. function flag_lists_template_submit($form, &$form_state) {
  604. $types = array_filter($form_state['values']['types']);
  605. // Clean out the old types, then add the new.
  606. $num_deleted = db_delete('flag_lists_types')
  607. ->condition('name', $form_state['values']['name'])
  608. ->execute();
  609. foreach ($types as $type) {
  610. db_insert('flag_lists_types')
  611. ->fields(array(
  612. 'name' => $form_state['values']['name'],
  613. 'type' => $type,
  614. ))
  615. ->execute();
  616. }
  617. }
  618. /**
  619. * Helper function to build an array of all lists available to or owned by the
  620. * current user and that are available on the current content type.
  621. */
  622. function flag_lists_get_content_fids() {
  623. global $user;
  624. // This is a node view. We only care about nodes for now.
  625. if (arg(0) == 'node' && is_numeric(arg(1)) && is_null(arg(2))) {
  626. $type = db_select('node', 'n')
  627. ->fields('n', array('type'))
  628. ->condition('nid', arg(1))
  629. ->execute()
  630. ->fetchField();
  631. // Get current user's flags for this node.
  632. $query = db_select('flag_lists', 'fc')
  633. ->fields('f', 'fid')
  634. ->condition('fc.uid', $user->uid)
  635. ->condition('fn.type', $type);
  636. $query->leftJoin('flag_types', 'fn', 'fn.fid = fc.fid');
  637. $query->leftJoin('flag', 'f', 'fc.fid = f.fid');
  638. $fc_result = $query->execute();
  639. foreach ($fc_result as $row) {
  640. $fids[] = $row->fid;
  641. }
  642. }
  643. // This is the flag / unflag callback
  644. elseif (arg(0) == 'flag' && (arg(1) == 'flag' || arg(1) == 'unflag')) {
  645. // Get the flag for this request.
  646. $fids[] = db_select('flag', 'f')
  647. ->fields('f', array('fid'))
  648. ->condition('name', arg(2))
  649. ->execute()
  650. ->fetchField();
  651. }
  652. // Get the regular flags for this node. The flag module will narrow by role,
  653. // etc. when flag_get_flags() is called. These flag ids are always returned.
  654. $query = db_select('flag', 'f')
  655. ->fields('f', array('fid'))
  656. ->condition('fc.fid', NULL);
  657. $query->leftJoin('flag_lists', 'fc', 'fc.fid = f.fid');
  658. $f_result = $query->execute();
  659. foreach ($f_result as $obj) {
  660. $fids[] = $obj->fid;
  661. }
  662. if (is_array($fids)) {
  663. return array_unique($fids);
  664. }
  665. else {
  666. return array();
  667. }
  668. }
  669. /**
  670. * Implements hook_block_info();
  671. */
  672. function flag_lists_block_info() {
  673. return array(
  674. 'flag_lists' => array(
  675. 'info' => drupal_ucfirst(t('@name', array('@name' => variable_get('flag_lists_name','list')))),
  676. 'cache' => DRUPAL_NO_CACHE,
  677. ),
  678. 'flag_lists_list' => array(
  679. 'info' => t('My @name', array('@name' => variable_get('flag_lists_name', 'list'))),
  680. 'cache' => DRUPAL_NO_CACHE,
  681. ),
  682. );
  683. }
  684. /**
  685. * Implements hook_block_configure().
  686. */
  687. function flag_lists_block_configure($delta = '') {
  688. $form = array();
  689. switch ($delta) {
  690. case 'flag_lists':
  691. $form = array(
  692. 'create_lists' => array(
  693. '#type' => 'checkbox',
  694. '#title' => t('Show link to add new list'),
  695. '#default_value' => variable_get('flag_lists_create_lists', 1),
  696. '#description' => t('Checking this adds a link to the create new list form.'),
  697. ),
  698. 'ops' => array(
  699. '#type' => 'checkbox',
  700. '#title' => t('Show edit and delete links'),
  701. '#default_value' => variable_get('flag_lists_ops', 1),
  702. '#description' => t('Checking this appends edit and delete links to each list name for users with access.'),
  703. ),
  704. 'include_flags' => array(
  705. '#type' => 'checkbox',
  706. '#title' => t('Include flag module flags'),
  707. '#default_value' => variable_get('flag_lists_include_flags', 0),
  708. '#description' => t('Checking this will append flag module flags to the list of lists.'),
  709. ),
  710. );
  711. break;
  712. }
  713. return $form;
  714. }
  715. /**
  716. * Implements hook_block_save().
  717. */
  718. function flag_lists_block_save($delta = '', $edit = array()) {
  719. switch ($delta) {
  720. case 'flag_lists':
  721. variable_set('flag_lists_create_lists', $edit['create_lists']);
  722. variable_set('flag_lists_ops', $edit['ops']);
  723. variable_set('flag_lists_include_flags', $edit['include_flags']);
  724. break;
  725. }
  726. }
  727. /**
  728. * Implements hook_block_view().
  729. */
  730. function flag_lists_block_view($delta = '') {
  731. $block = array();
  732. switch ($delta) {
  733. case 'flag_lists':
  734. if (user_access('create flag lists')) {
  735. $block = array(
  736. 'subject' => t('My @name', array('@name' => variable_get('flag_lists_name', 'list'))),
  737. 'content' => theme('flag_lists_list', array(
  738. 'node' => NULL,
  739. 'create' => variable_get('flag_lists_create_lists', 0),
  740. 'ops' => variable_get('flag_lists_ops', 0),
  741. 'use_flags' => variable_get('flag_lists_include_flags', 0)
  742. )),
  743. );
  744. }
  745. break;
  746. case 'flag_lists_list':
  747. if (user_access('create flag lists')) {
  748. global $user;
  749. $account = user_load($user->uid);
  750. $block = array(
  751. 'subject' => t('My @name', array('@name' => variable_get('flag_lists_name', 'list'))),
  752. 'content' => flag_lists_user_page($account),
  753. );
  754. }
  755. break;
  756. }
  757. return (!empty($block['content'])) ? $block : array();
  758. }
  759. /**
  760. * Implementation of hook_user_delete().
  761. */
  762. function flag_lists_user_delete($account) {
  763. // Remove personal flags by this user.
  764. $num_deleted = db_delete('flag_lists_flags')
  765. ->condition('uid', $account->uid)
  766. ->execute();
  767. }
  768. /**
  769. * Build a flag's messages.
  770. */
  771. function flag_lists_set_messages(&$flag) {
  772. // Get the parent flag. These are cached by the flag module.
  773. $pflag = flag_get_flag(NULL, $flag->pfid);
  774. $title = $flag->title;
  775. $lists_name = t('@name', array('@name' => variable_get('flag_lists_name', 'list')));
  776. $flag->flag_short = $pflag->flag_short;
  777. $flag->flag_long = $pflag->flag_long;
  778. $flag->flag_message = $pflag->flag_message;
  779. $flag->unflag_short = $pflag->unflag_short;
  780. $flag->unflag_long = $pflag->unflag_long;
  781. $flag->unflag_message = $pflag->unflag_message;
  782. }
  783. /**
  784. * Implementation of hook_flag_access().
  785. *
  786. * Make sure a user can only see his/her own personal flags.
  787. */
  788. function flag_lists_flag_access($flag, $entity_id, $action, $account) {
  789. if ( !empty($flag->name) && (strpos($flag->name, 'flag_lists') === 0) ) {
  790. switch ($action) {
  791. case 'flag':
  792. case 'unflag':
  793. $fid = db_select('flag_lists_flags', 'f')
  794. ->fields('f')
  795. ->condition('f.uid', $account->uid)
  796. ->execute()
  797. ->fetchField();
  798. if (!empty($fid)) {
  799. return array('flag_lists' => TRUE);
  800. }
  801. else {
  802. return array('flag_lists' => FALSE);
  803. }
  804. }
  805. }
  806. }
  807. /**
  808. * Implementation of hook_link().
  809. */
  810. // There may be a better way to keep flag lists out of the links, but this
  811. // works for now. @todo Find a better way to keep flags lists out of links.
  812. function flag_lists_link_alter(&$links, $node) {
  813. if (!variable_get('flag_lists_use_links', 1)) {
  814. foreach ($links as $name => $link) {
  815. if (stristr($name, 'flag-fl_')) {
  816. unset($links[$name]);
  817. }
  818. }
  819. }
  820. }
  821. /**
  822. * Implementation of hook_flag_alter().
  823. */
  824. function flag_lists_flag_alter(&$flag) {
  825. }
  826. /**
  827. * Implementation of hook_flag_delete().
  828. *
  829. * This is not in flag yet.
  830. */
  831. function flag_lists_flag_delete($flag) {
  832. // Template flag is being deleted. Clean up our tables.
  833. // Collect the sub-flag fids so we can delete counts and content records.
  834. $results = db_select('flag_lists_flags', 'f')
  835. ->fields('f', array('fid', 'name'))
  836. ->condition('pfid', $flag->fid)
  837. ->execute();
  838. foreach ($results as $fid) {
  839. db_delete('flag_lists_counts')
  840. ->condition('fid', $flag->fid)
  841. ->execute();
  842. db_delete('flag_lists_content')
  843. ->condition('fid', $flag->fid)
  844. ->execute();
  845. }
  846. // flag_lists_types uses the template flag name, not our own fid.
  847. db_delete('flag_lists_types')
  848. ->condition('name', $flag->name)
  849. ->execute();
  850. // Now delete the sub-flags.
  851. $num_deleted = db_delete('flag_lists_flags')
  852. ->condition('pfid', $flag->fid)
  853. ->execute();
  854. if (!empty($num_deleted)) {
  855. drupal_set_message(t('The template flag "@title" and all its sub-flags have been deleted.', array('@title' => $flag->title)));
  856. }
  857. }
  858. /**
  859. * Implementation of hook_views_api().
  860. */
  861. function flag_lists_views_api() {
  862. return array(
  863. 'api' => 3.0,
  864. 'path' => drupal_get_path('module', 'flag_lists') . '/includes',
  865. );
  866. }
  867. /**
  868. * Helper function to test if a flag is owned by the current user, or current
  869. * user can administer flags.
  870. */
  871. function flag_lists_is_owner($action, $name) {
  872. global $user;
  873. if (user_access('administer flags')) {
  874. return TRUE;
  875. }
  876. // If we don't have an fid, then we have the flag name.
  877. if (is_numeric($name)) {
  878. $query = db_select('flag_lists_flags', 'f')->condition('fid', $name);
  879. $query->addField('f', 'name');
  880. $name = $query->execute()->fetchField();
  881. }
  882. if (!user_access($action . ' own flag lists')) {
  883. return FALSE;
  884. }
  885. if (db_select('flag_lists_flags', 'f')->fields('f')->condition('f.name', $name)->condition('f.uid', $user->uid)
  886. ->countQuery()->execute()->fetchField()
  887. ) {
  888. return TRUE;
  889. }
  890. return FALSE;
  891. }
  892. /**
  893. * Get a single user's lists, and merge in flag module flags
  894. */
  895. function flag_lists_get_user_flags($content_type = NULL, $account = NULL, $use_flags = FALSE) {
  896. $flags = array();
  897. $lists = array();
  898. if (!isset($account)) {
  899. $account = $GLOBALS['user'];
  900. }
  901. // Get flag lists flags
  902. $query = db_select('flag_lists_flags', 'fl')
  903. ->fields('fl')
  904. ->condition('fl.uid', $account->uid);
  905. $query->leftJoin('flag', 'f', 'fl.pfid = f.fid');
  906. $query->leftJoin('flag_lists_types', 'ft', 'ft.name = f.name');
  907. $query->addField('ft', 'type');
  908. if ($content_type) {
  909. $query->condition('ft.type', $content_type);
  910. }
  911. $result = $query->execute();
  912. foreach ($result as $row) {
  913. if (!isset($lists[$row->name])) {
  914. $lists[$row->name] = flag_flag::factory_by_row($row);
  915. $lists[$row->name]->module = 'flag_lists';
  916. }
  917. else {
  918. $lists[$row->name]->types[] = $row->type;
  919. }
  920. }
  921. // Get regular flags.
  922. if ($use_flags) {
  923. $flags = flag_get_flags('node', $content_type, $account);
  924. foreach ($flags as $key => $flag) {
  925. if (!isset($flag->module)) {
  926. // Assume flag is from flag module
  927. $flags[$key]->module = 'flag';
  928. }
  929. // Strip out any list templates
  930. if (stristr($flag->name, 'fl_template') !== FALSE) {
  931. unset($flags[$key]);
  932. }
  933. }
  934. }
  935. $flags = array_merge($lists, $flags);
  936. return $flags;
  937. }
  938. /**
  939. * Theme function to return edit, delete links.
  940. *
  941. * @param $flag
  942. * The flag whose links are being built.
  943. */
  944. function theme_flag_lists_ops($variables) {
  945. $flag = $variables['flag'];
  946. $links = array(
  947. 'flags_edit' => array(
  948. 'title' => t('edit'),
  949. 'href' => 'flag/lists/edit/' . $flag->name,
  950. 'query' => drupal_get_destination()
  951. ),
  952. 'flags_delete' => array(
  953. 'title' => t('delete'),
  954. 'href' => 'flag/lists/delete/' . $flag->name,
  955. 'query' => drupal_get_destination()
  956. ),
  957. );
  958. return theme('links', array('links' => $links, 'attributes' => array('class' => 'flag_lists_ops')));
  959. }
  960. /**
  961. * Theme a list of lists
  962. *
  963. * @param $node
  964. * The listable node
  965. * @param boolean $create
  966. * Show the create list form.
  967. * @param boolean $ops
  968. * Show the edit / delete links for lists
  969. * @param boolean $use_flags
  970. * Show flags from the flag module
  971. * @return <type>
  972. */
  973. // @todo Separate out the code from the theming better.
  974. function theme_flag_lists_list($variables) {
  975. $node = $variables['node'];
  976. $create = $variables['create'];
  977. $ops = $variables['ops'];
  978. $use_flags = $variables['use_flags'];
  979. $items = array();
  980. // Make sure we have a node.
  981. if (is_object($node) && user_access('create flag lists')) {
  982. $content_type = $node->type;
  983. $entity_id = $node->nid;
  984. }
  985. // Or at least confirm we are on a node page and use has access.
  986. elseif (arg(0) == 'node' && is_numeric(arg(1)) && user_access('create flag lists')) {
  987. $entity_id = arg(1);
  988. $query = db_select('node')->condition('nid', $entity_id);
  989. $query->addField('node', 'type');
  990. $content_type = $query->execute()->fetchField();
  991. }
  992. else {
  993. return;
  994. }
  995. // Do we have a list template for this node type, or are we s
  996. if (!flag_lists_template_exists($content_type) && !$use_flags) {
  997. return;
  998. }
  999. global $user;
  1000. if ($flags = flag_lists_get_user_flags($content_type, $user, $use_flags)) {
  1001. // Build the list of lists for this node.
  1002. foreach ($flags as $flag) {
  1003. if ($flag->module == 'flag_lists') {
  1004. $action = _flag_lists_is_flagged($flag, $entity_id, $user->uid, 0) ? 'unflag' : 'flag';
  1005. }
  1006. else {
  1007. $action = $flag->is_flagged($entity_id) ? 'unflag' : 'flag';;
  1008. }
  1009. // Do we need the ops?
  1010. if ($ops && $flag->module == 'flag_lists') {
  1011. $ops_links = theme('flag_lists_ops', array('flag' => $flag));
  1012. $link = $flag->theme($action, $entity_id) . $ops_links;
  1013. }
  1014. else {
  1015. $link = $flag->theme($action, $entity_id);
  1016. }
  1017. // If it's a list, fix the link.
  1018. if ($flag->module == 'flag_lists') {
  1019. flag_lists_fix_link($link, $action);
  1020. }
  1021. $items[] = $link;
  1022. }
  1023. }
  1024. if ($create && flag_lists_template_exists($content_type)) {
  1025. $items[] = l(t('Make a new @name', array('@name' => variable_get('flag_lists_name', t('list')))), 'flag-lists/add/' . $content_type, array('query' => drupal_get_destination()));
  1026. }
  1027. // Return if nothing to display.
  1028. if (empty($items) || !count($items)) {
  1029. return;
  1030. }
  1031. drupal_add_css(drupal_get_path('module', 'flag_lists') . '/theme/flag_lists.css');
  1032. return theme('item_list', array(
  1033. 'items' => $items,
  1034. 'type' => 'ul',
  1035. 'attributes' => array('class' => 'flag-lists-links')
  1036. ));
  1037. }
  1038. // Do we still need this, and/or do we need our own cache?
  1039. /**
  1040. * Clear the flag cache.
  1041. *
  1042. * This is a less severe cache clear than provided by flag. All flag lists
  1043. * users must be authorized, so we don't need to flush the page cache. For now,
  1044. * flag lists titles won't be in the menu, so no need to clear that.
  1045. */
  1046. function _flag_lists_clear_cache() {
  1047. // We're not using _flag_clear_cache because we probably don't need the menu
  1048. // rebuild and don't need to clear the page cache.
  1049. if (module_exists('views')) {
  1050. views_invalidate_cache();
  1051. }
  1052. }
  1053. /**
  1054. * Update ALL flag lists with settings form values.
  1055. */
  1056. function flag_lists_rebuild() {
  1057. $flags = flag_lists_get_flags();
  1058. foreach ($flags as $flag) {
  1059. flag_lists_set_messages($flag);
  1060. $flag->link_type = 'toggle';
  1061. flag_lists_save($flag);
  1062. }
  1063. }
  1064. /**
  1065. * Build array of all flag lists.
  1066. *
  1067. * @return If limit and header arguments are provided, the paged flags, otherwise
  1068. * an array of all flags.
  1069. */
  1070. function flag_lists_get_flags($limit = NULL, $header = NULL) {
  1071. $flags = array();
  1072. if ($limit) {
  1073. $query = db_select('flag_lists_flags', 'fl')
  1074. ->fields('fl')
  1075. ->groupBy('fl.fid')
  1076. ->extend('PagerDefault')
  1077. ->limit($limit);
  1078. $query->leftJoin('flag_types', 'ft', 'ft.fid = fl.pfid');
  1079. $query->addExpression('GROUP_CONCAT(ft.type)', 'types');
  1080. $result = $query->execute();
  1081. }
  1082. else {
  1083. $query = db_select('flag_lists_flags', 'fl')
  1084. ->fields('fl')
  1085. ->groupBy('fl.fid');
  1086. $query->leftJoin('flag_types', 'ft', 'ft.fid = fl.pfid');
  1087. $query->addExpression('GROUP_CONCAT(ft.type)', 'types');
  1088. $result = $query->execute();
  1089. }
  1090. foreach ($result as $row) {
  1091. $flags[$row->name] = flag_flag::factory_by_row($row);
  1092. $flags[$row->name]->types = explode(',', $row->types);
  1093. $flags[$row->name]->uid = $row->uid;
  1094. }
  1095. return $flags;
  1096. }
  1097. /**
  1098. * Get a specific flag.
  1099. *
  1100. * Using this instead of flag_get_flag() for performance.
  1101. */
  1102. function flag_lists_get_flag($fid) {
  1103. // If we don't have an fid, then we have the flag name.
  1104. if (!is_numeric($fid)) {
  1105. $query = db_select('flag_lists_flags')
  1106. ->condition('name', $fid);
  1107. $query->addField('flag_lists_flags', 'fid');
  1108. $fid = $query->execute()->fetchField();
  1109. }
  1110. $query = db_select('flag_lists_flags', 'fl')
  1111. ->fields('fl')
  1112. ->condition('fl.fid', $fid);
  1113. $query->leftJoin('flag_types', 'ft', 'ft.fid = fl.pfid');
  1114. $query->addField('ft', 'type');
  1115. $result = $query->execute();
  1116. foreach ($result as $row) {
  1117. if (!isset($flag->name)) {
  1118. $flag = flag_flag::factory_by_row($row);
  1119. }
  1120. else {
  1121. $flag->types[] = $row->type;
  1122. }
  1123. }
  1124. return $flag;
  1125. }
  1126. /**
  1127. * Get all flagged content in a flag.
  1128. *
  1129. * Using this instead of flag_get_flagged_content() because we need to make sure that we use flag_lists_get_flags()
  1130. *
  1131. * @param
  1132. * The flag name for which to retrieve flagged content.
  1133. */
  1134. function flag_lists_get_flagged_content($fid, $uid) {
  1135. $return = array();
  1136. $flag = flag_lists_get_flag($fid);
  1137. $result = db_select('flag_lists_content', 'f')
  1138. ->fields('f')
  1139. ->condition('f.fid', $flag->fid)
  1140. ->condition('f.uid', $uid)
  1141. ->execute();
  1142. foreach ($result as $row) {
  1143. $return[] = $row;
  1144. }
  1145. return $return;
  1146. }
  1147. /**
  1148. * Implementation of hook_flag_link().
  1149. *
  1150. * When Flag uses a link type provided by this module, it will call this
  1151. * implementation of hook_flag_link(). It returns a single link's attributes,
  1152. * using the same structure as hook_link(). Note that "title" is provided by
  1153. * the Flag configuration if not specified here.
  1154. *
  1155. * @param $flag
  1156. * The full flag object of for the flag link being generated.
  1157. * @param $action
  1158. * The action this link will perform. Either 'flag' or 'unflag'.
  1159. * @param $entity_id
  1160. * The ID of the node, comment, user, or other object being flagged.
  1161. * @return
  1162. * An array defining properties of the link.
  1163. */
  1164. function flag_lists_flag_link($flag, $action, $entity_id) {
  1165. return array();
  1166. }
  1167. /**
  1168. * Implementation of hook_flag_link_type_info().
  1169. */
  1170. function flag_lists_flag_link_type_info() {
  1171. return array(
  1172. 'fl_template' => array(
  1173. 'title' => t('Flag Lists toggle'),
  1174. 'description' => t('If you are creating a Flag lists template flag, you must select this link type.'),
  1175. 'options' => array(),
  1176. 'uses standard js' => TRUE,
  1177. 'uses standard css' => TRUE,
  1178. ),
  1179. );
  1180. }
  1181. function flag_lists_flag_default_flags() {
  1182. $flags = array();
  1183. // Exported flag: "Flag lists template".
  1184. $flags['fl_template'] = array(
  1185. 'entity_type' => 'node',
  1186. 'title' => 'Flag lists template',
  1187. 'global' => 0,
  1188. 'types' => array(),
  1189. 'flag_short' => 'Add to your [flag_lists:title] [flag_lists:term]',
  1190. 'flag_long' => 'Add this post to your [flag_lists:title] [flag_lists:term]',
  1191. 'flag_message' => 'This post has been added to your [flag_lists:title] [flag_lists:term]',
  1192. 'unflag_short' => 'Remove this from your [flag_lists:title] [flag_lists:term]',
  1193. 'unflag_long' => 'Remove this post from your [flag_lists:title] [flag_lists:term]',
  1194. 'unflag_message' => 'This post has been removed from your [flag_lists:title] [flag_lists:term]',
  1195. 'unflag_denied_text' => '',
  1196. 'link_type' => 'toggle',
  1197. 'weight' => 0,
  1198. 'api_version' => 3,
  1199. 'module' => 'flag_lists',
  1200. 'show_on_page' => 0,
  1201. 'show_on_teaser' => 0,
  1202. 'show_on_form' => 0,
  1203. 'status' => FALSE,
  1204. 'import_roles' => array(
  1205. 'flag' => array(),
  1206. 'unflag' => array(),
  1207. ),
  1208. );
  1209. return $flags;
  1210. }
  1211. /**
  1212. * Saves a flag to the database. It is a wrapper around update($flag) and insert($flag).
  1213. */
  1214. function flag_lists_save(&$flag, $account = NULL) {
  1215. if (!isset($account)) {
  1216. $account = $GLOBALS['user'];
  1217. }
  1218. if (isset($flag->fid)) {
  1219. flag_lists_update($flag);
  1220. $flag->is_new = FALSE;
  1221. module_invoke_all('flag_lists', $flag, $account);
  1222. }
  1223. else {
  1224. flag_lists_insert($flag);
  1225. $flag->is_new = TRUE;
  1226. module_invoke_all('flag_lists', $flag, $account);
  1227. }
  1228. // Clear the page cache for anonymous users.
  1229. // cache_clear_all('*', 'cache_page', TRUE);
  1230. }
  1231. /**
  1232. * Saves an existing flag to the database. Better use save($flag).
  1233. */
  1234. function flag_lists_update($flag) {
  1235. $num_updated = db_update('flag_lists_flags')
  1236. ->fields(array(
  1237. 'title' => $flag->title,
  1238. 'name' => $flag->name,
  1239. 'options' => $flag->get_serialized_options($flag),
  1240. ))
  1241. ->condition('fid', $flag->fid)
  1242. ->execute();
  1243. }
  1244. /**
  1245. * Saves a new flag to the database. Better use save($flag).
  1246. */
  1247. function flag_lists_insert($flag) {
  1248. $flag->fid = db_insert('flag_lists_flags')
  1249. ->fields(array(
  1250. 'pfid' => $flag->pfid,
  1251. 'uid' => $flag->uid,
  1252. 'entity_type' => $flag->entity_type,
  1253. 'name' => $flag->name,
  1254. 'title' => $flag->title,
  1255. 'options' => $flag->get_serialized_options($flag),
  1256. ))
  1257. ->execute();
  1258. $flag->name = 'flag_lists_' . $flag->uid . '_' . $flag->fid;
  1259. flag_lists_update($flag);
  1260. }
  1261. /**
  1262. * Delete a flag_lists flag.
  1263. *
  1264. */
  1265. function flag_lists_fl_delete($flag, $account = NULL) {
  1266. if (!isset($account)) {
  1267. $account = $GLOBALS['user'];
  1268. }
  1269. db_delete('flag_lists_counts')->condition('fid', $flag->fid)->execute();
  1270. db_delete('flag_lists_content')->condition('fid', $flag->fid)->execute();
  1271. db_delete('flag_lists_flags')->condition('fid', $flag->fid)->execute();
  1272. $flag->is_deleted = TRUE;
  1273. module_invoke_all('flag_lists', $flag, $account);
  1274. _flag_lists_clear_cache();
  1275. drupal_set_message(t('The @name @title has been deleted.', array('@name' => variable_get('flag_lists_name', 'list'), '@title' => $flag->title)));
  1276. }
  1277. /**
  1278. * Menu callback for (un)flagging a node.
  1279. *
  1280. * Used both for the regular callback as well as the JS version. We use this
  1281. * instead of the flag module's because our flags are not in the flags table.
  1282. */
  1283. function flag_lists_page($action = NULL, $flag_name = NULL, $entity_id = NULL) {
  1284. global $user;
  1285. // Shorten up the variables that affect the behavior of this page.
  1286. $js = isset($_REQUEST['js']);
  1287. $token = isset($_REQUEST['token']) ? $_REQUEST['token'] : '';
  1288. // Specifically $_GET to avoid getting the $_COOKIE variable by the same key.
  1289. $has_js = isset($_GET['has_js']);
  1290. // Check the flag token, then perform the flagging.
  1291. if (!flag_check_token($token, $entity_id)) {
  1292. $error = t('Bad token. You seem to have followed an invalid link.');
  1293. }
  1294. elseif ($user->uid == 0 && !$has_js) {
  1295. $error = t('You must have JavaScript and cookies enabled in your browser to flag content.');
  1296. }
  1297. else {
  1298. if (empty($flag_name) || !($flag = flag_lists_get_flag($flag_name))) {
  1299. // Flag does not exist.
  1300. $error = t('You are not allowed to flag, or unflag, this content.');
  1301. }
  1302. // Identify it as ours.
  1303. $flag->module = 'flag_lists';
  1304. flag_lists_do_flag($flag, $action, $entity_id);
  1305. }
  1306. // If an error was received, set a message and exit.
  1307. if (isset($error)) {
  1308. if ($js) {
  1309. drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8');
  1310. print drupal_to_js(array(
  1311. 'status' => FALSE,
  1312. 'errorMessage' => $error,
  1313. ));
  1314. exit;
  1315. }
  1316. else {
  1317. drupal_set_message($error);
  1318. drupal_access_denied();
  1319. exit;
  1320. }
  1321. }
  1322. // If successful, return data according to the request type.
  1323. if ($js) {
  1324. drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8');
  1325. // $flag = flag_lists_get_flag($flag_name);
  1326. // $flag->link_type = 'toggle';
  1327. $sid = flag_get_sid($user->uid);
  1328. $new_action = _flag_lists_is_flagged($flag, $entity_id, $user->uid, $sid) ? 'unflag' : 'flag';
  1329. $new_link = $flag->theme($new_action, $entity_id, array("after_flagging" => TRUE));
  1330. flag_lists_fix_link($new_link, $new_action);
  1331. drupal_json_output(array(
  1332. 'status' => TRUE,
  1333. 'newLink' => $new_link,
  1334. // Further information for the benefit of custom JavaScript event handlers:
  1335. 'contentId' => $entity_id,
  1336. 'contentType' => $flag->entity_type,
  1337. 'flagName' => $flag->name,
  1338. 'flagStatus' => $action,
  1339. ));
  1340. exit;
  1341. }
  1342. else {
  1343. $flag = flag_lists_get_flag($flag->fid);
  1344. drupal_set_message($flag->get_label($action . '_message', $entity_id));
  1345. drupal_goto();
  1346. }
  1347. }
  1348. function flag_lists_fix_link(&$link, $action) {
  1349. // This is a hack to let us use our own flag/unflag callbacks without having
  1350. // to override $flag->theme and creating our own flag_link type.
  1351. $link = str_replace('/flag/' . $action . '/', '/flag-lists/' . $action . '/', $link);
  1352. }
  1353. /**
  1354. * Remove all entries of entity_id and type
  1355. *
  1356. * @param $entity_id
  1357. * Entity id which has been flagged.
  1358. * @param $type
  1359. * The entity type.
  1360. */
  1361. function _flag_lists_remove_entity($entity_id, $type) {
  1362. $query = db_select('flag_lists_content')
  1363. ->condition('entity_id', $entity_id)
  1364. ->condition('entity_type', $type);
  1365. $query->fields('flag_lists_content', array('fcid', 'fid', 'uid', 'sid'));
  1366. $items = $query->execute()->fetchAll();
  1367. if ($items) {
  1368. foreach ($items as $key => $value) {
  1369. db_delete('flag_lists_content')
  1370. ->condition('fcid', $value->fcid)
  1371. ->execute();
  1372. watchdog('flag_lists', t('Deleted entry @fcid from flat_lists_content', array('@fcid' => $value->fcid)));
  1373. }
  1374. }
  1375. }
  1376. /**
  1377. * Implements hook_entity_delete
  1378. */
  1379. function flag_lists_entity_delete($entity, $type) {
  1380. foreach (flag_get_flags($type) as $flag) {
  1381. if (isset($entity->vid)) {
  1382. $items = _flag_lists_remove_entity($entity->vid, $type);
  1383. }
  1384. }
  1385. }
  1386. /**
  1387. * Flags, or unflags, an item.
  1388. *
  1389. * @param $action
  1390. * Either 'flag' or 'unflag'.
  1391. * @param $entity_id
  1392. * The ID of the item to flag or unflag.
  1393. * @param $account
  1394. * The user on whose behalf to flag. Leave empty for the current user.
  1395. * @param $skip_permission_check
  1396. * Flag the item even if the $account user doesn't have permission to do so.
  1397. * @return
  1398. * FALSE if some error occured (e.g., user has no permission, flag isn't
  1399. * applicable to the item, etc.), TRUE otherwise.
  1400. */
  1401. function flag_lists_do_flag($flag, $action, $entity_id, $account = NULL, $skip_permission_check = FALSE) {
  1402. if (!isset($account)) {
  1403. $account = $GLOBALS['user'];
  1404. }
  1405. if (!$account) {
  1406. return FALSE;
  1407. }
  1408. if (!$skip_permission_check) {
  1409. if (!$flag->access($entity_id, $action, $account)) {
  1410. // User has no permission to flag/unflag this object.
  1411. return FALSE;
  1412. }
  1413. }
  1414. else {
  1415. // We are skipping permission checks. However, at a minimum we must make
  1416. // sure the flag applies to this content type:
  1417. if (!$flag->applies_to_entity_id($entity_id)) {
  1418. return FALSE;
  1419. }
  1420. }
  1421. // Clear various caches; We don't want code running after us to report
  1422. // wrong counts or false flaggings.
  1423. // flag_get_counts(NULL, NULL, TRUE);
  1424. // flag_get_user_flags(NULL, NULL, NULL, NULL, TRUE);
  1425. // Find out which user id to use.
  1426. $uid = $flag->global ? 0 : $account->uid;
  1427. $sid = flag_get_sid($uid);
  1428. // Anonymous users must always have a session id.
  1429. if ($sid == 0 && $account->uid == 0) {
  1430. return FALSE;
  1431. }
  1432. // Perform the flagging or unflagging of this flag. We invoke hook_flag here
  1433. // because we do our own flagging.
  1434. $flagged = _flag_lists_is_flagged($flag, $entity_id, $uid, $sid);
  1435. if ($action == 'unflag') {
  1436. if ($flagged) {
  1437. $fcid = _flag_lists_unflag($flag, $entity_id, $uid, $sid);
  1438. module_invoke_all('flag_unflag', $flag, $entity_id, $account, $fcid);
  1439. return TRUE;
  1440. }
  1441. }
  1442. elseif ($action == 'flag') {
  1443. if (!$flagged) {
  1444. $fcid = _flag_lists_flag($flag, $entity_id, $uid, $sid);
  1445. module_invoke_all('flag_flag', $flag, $entity_id, $account, $fcid);
  1446. return TRUE;
  1447. }
  1448. }
  1449. return FALSE;
  1450. }
  1451. /**
  1452. * Returns TRUE if a certain user has flagged this content.
  1453. *
  1454. *
  1455. * This method is similar to is_flagged() except that it does direct SQL and
  1456. * doesn't do caching. Use it when you want to not affect the cache, or to
  1457. * bypass it.
  1458. *
  1459. */
  1460. function _flag_lists_is_flagged($flag, $entity_id, $uid, $sid) {
  1461. $query = db_select('flag_lists_content')
  1462. ->condition('fid', $flag->fid)
  1463. ->condition('uid', $uid)
  1464. ->condition('sid', $sid)
  1465. ->condition('entity_id', $entity_id);
  1466. $query->addField('flag_lists_content', 'fid');
  1467. return $query->execute()->fetchField();
  1468. }
  1469. /**
  1470. * A low-level method to flag content.
  1471. *
  1472. * You probably shouldn't call this raw private method: call the
  1473. * flag_lists_do_flag() function instead.
  1474. *
  1475. */
  1476. function _flag_lists_flag($flag, $entity_id, $uid, $sid) {
  1477. $fcid = db_insert('flag_lists_content')
  1478. ->fields(array(
  1479. 'fid' => $flag->fid,
  1480. 'entity_type' => $flag->entity_type,
  1481. 'entity_id' => $entity_id,
  1482. 'uid' => $uid,
  1483. 'sid' => $sid,
  1484. 'timestamp' => REQUEST_TIME,
  1485. ))
  1486. ->execute();
  1487. _flag_lists_update_count($flag, $entity_id);
  1488. return $fcid;
  1489. }
  1490. /**
  1491. * A low-level method to unflag content.
  1492. *
  1493. * You probably shouldn't call this raw private method: call the
  1494. * flag_lists_do_flag() function instead.
  1495. *
  1496. */
  1497. function _flag_lists_unflag($flag, $entity_id, $uid, $sid) {
  1498. $query = db_select('flag_lists_content')
  1499. ->condition('fid', $flag->fid)
  1500. ->condition('entity_id', $entity_id)
  1501. ->condition('uid', $uid)
  1502. ->condition('sid', $sid);
  1503. $query->addField('flag_lists_content', 'fcid');
  1504. $fcid = $query->execute()->fetchField();
  1505. if ($fcid) {
  1506. db_delete('flag_lists_content')
  1507. ->condition('fcid', $fcid)
  1508. ->execute();
  1509. _flag_lists_update_count($flag, $entity_id);
  1510. }
  1511. return $fcid;
  1512. }
  1513. /**
  1514. * Updates the flag count for this content
  1515. */
  1516. function _flag_lists_update_count($flag, $entity_id) {
  1517. $count = db_select('flag_lists_content', 'f')
  1518. ->fields('f')
  1519. ->condition('fid', $flag->fid)
  1520. ->condition('entity_id', $entity_id)
  1521. ->countQuery()
  1522. ->execute()
  1523. ->fetchField();
  1524. if (empty($count)) {
  1525. $num_deleted = db_delete('flag_lists_counts')
  1526. ->condition('fid', $flag->fid)
  1527. ->condition('entity_id', $entity_id)
  1528. ->execute();
  1529. }
  1530. else {
  1531. $num_updated = db_update('flag_lists_counts')
  1532. ->fields(array(
  1533. 'count' => $count,
  1534. ))
  1535. ->condition('fid', $flag->fid)
  1536. ->condition('entity_id', $entity_id)
  1537. ->execute();
  1538. if (empty($num_updated)) {
  1539. db_insert('flag_lists_counts')
  1540. ->fields(array(
  1541. 'fid' => $flag->fid,
  1542. 'entity_type' => $flag->entity_type,
  1543. 'entity_id' => $entity_id,
  1544. 'count' => $count,
  1545. ))
  1546. ->execute();
  1547. }
  1548. }
  1549. }
  1550. /**
  1551. * Checks for a list template for a content type.
  1552. */
  1553. function flag_lists_template_exists($type) {
  1554. $query = db_select('flag_lists_types')
  1555. ->condition('type', $type);
  1556. $query->addField('flag_lists_types', 'type');
  1557. $exists = $query->execute()->fetchField();
  1558. if (!empty($exists)) {
  1559. return TRUE;
  1560. }
  1561. return FALSE;
  1562. }
  1563. /**
  1564. * Checks for a list title by node type.
  1565. */
  1566. function flag_lists_title_exists($title, $type) {
  1567. return db_query("SELECT COUNT(flf.fid) FROM {flag_lists_flags} flf LEFT JOIN {flag_types} ft ON flf.pfid=ft.fid WHERE flf.title=:title AND ft.type=:type AND flf.uid=:uid", array(
  1568. ':title' => $title,
  1569. ':type' => $type,
  1570. ':uid' => $GLOBALS['user']->uid
  1571. ))->fetchField();
  1572. }
  1573. /**
  1574. * Get a list of template flag names.
  1575. */
  1576. function flag_lists_get_templates() {
  1577. $templates = array();
  1578. $result = db_select('flag_lists_types', 'f')
  1579. ->fields('f', array(
  1580. 'name'
  1581. ))
  1582. ->distinct()
  1583. ->execute();
  1584. foreach ($result as $obj) {
  1585. $templates[] = flag_get_flag($obj->name);
  1586. }
  1587. return $templates;
  1588. }
  1589. /**
  1590. * Implements hook_token_info().
  1591. */
  1592. function flag_lists_token_info() {
  1593. $type = array(
  1594. 'name' => t('Flag lists'),
  1595. 'description' => t('Tokens related to flag lists.'),
  1596. 'needs-data' => 'flag_lists',
  1597. );
  1598. $flag_lists['term'] = array(
  1599. 'name' => t("Term"),
  1600. 'description' => t("The terminology used to name the lists, such as list, wishlist, favorites, etc."),
  1601. );
  1602. $flag_lists['title'] = array(
  1603. 'name' => t("Title"),
  1604. 'description' => t("The title of the list."),
  1605. );
  1606. return array(
  1607. 'types' => array('flag_lists' => $type),
  1608. 'tokens' => array('flag_lists' => $flag_lists),
  1609. );
  1610. }
  1611. /**
  1612. * Implements hook_tokens().
  1613. */
  1614. function flag_lists_tokens($type, $tokens, array $data = array(), array $options = array()) {
  1615. $sanitize = !empty($options['sanitize']);
  1616. $replacements = array();
  1617. if ($type == 'flag_lists' && !empty($data['flag_lists'])) {
  1618. $flag_list = $data['flag_lists'];
  1619. foreach ($tokens as $name => $original) {
  1620. switch ($name) {
  1621. case 'title':
  1622. $replacements[$original] = $sanitize ? check_plain($flag_list->title) : $flag_lists->title;
  1623. break;
  1624. case 'term':
  1625. $replacements[$original] = $sanitize ? check_plain(variable_get('flag_lists_name', 'list')) : variable_get('flag_lists_name', 'list');
  1626. break;
  1627. }
  1628. }
  1629. }
  1630. return $replacements;
  1631. }
  1632. /**
  1633. * Preprocess link title and text for the flag.tpl.php
  1634. *
  1635. * This seems to be the only place to do this
  1636. */
  1637. function flag_lists_preprocess_flag(&$variables) {
  1638. if (module_exists('token') && !empty($variables['flag']->module) && $variables['flag']->module == 'flag_lists') {
  1639. if (!empty($variables['link_text'])) {
  1640. $variables['link_text'] = token_replace($variables['link_text'], array('flag_lists' => $variables['flag']));
  1641. }
  1642. if (!empty($variables['link_title'])) {
  1643. $variables['link_title'] = token_replace($variables['link_title'], array('flag_lists' => $variables['flag']));
  1644. }
  1645. if (!empty($variables['message_text'])) {
  1646. $variables['message_text'] = token_replace($variables['message_text'], array('flag_lists' => $variables['flag']));
  1647. }
  1648. }
  1649. }
  1650. /**
  1651. * Implements hook_views_form_substitutions().
  1652. */
  1653. function flag_lists_views_form_substitutions() {
  1654. // Views check_plains the column label, so Flag lists needs to do the same
  1655. // in order for the replace operation to succeed.
  1656. $select_all_placeholder = check_plain('<!--flag-lists-ops-select-all-->');
  1657. $select_all = array(
  1658. '#type' => 'checkbox',
  1659. '#default_value' => FALSE,
  1660. '#attributes' => array('class' => array('flo-table-select-all'),
  1661. 'title' => array(t('Select all'))),
  1662. );
  1663. return array(
  1664. $select_all_placeholder => drupal_render($select_all),
  1665. );
  1666. }