flag.module 83 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615
  1. <?php
  2. /**
  3. * @file
  4. * The Flag module.
  5. */
  6. define('FLAG_API_VERSION', 3);
  7. define('FLAG_ADMIN_PATH', 'admin/structure/flags');
  8. define('FLAG_ADMIN_PATH_START', 3);
  9. /**
  10. * Implements hook_entity_info().
  11. */
  12. function flag_entity_info() {
  13. $return = array(
  14. 'flagging' => array(
  15. 'label' => t('Flagging'),
  16. 'controller class' => 'FlaggingController',
  17. 'base table' => 'flagging',
  18. 'fieldable' => TRUE,
  19. 'entity keys' => array(
  20. 'id' => 'flagging_id',
  21. 'bundle' => 'flag_name',
  22. ),
  23. // The following tells Field UI how to extract the bundle name from a
  24. // $flag object when we're visiting ?q=admin/.../manage/%flag/fields.
  25. 'bundle keys' => array(
  26. 'bundle' => 'name',
  27. ),
  28. 'bundles' => array(),
  29. // The following tells EntityAPI how to save flaggings, thus allowing use
  30. // of Entity metadata wrappers (if present).
  31. 'save callback' => 'flagging_save',
  32. 'creation callback' => 'flagging_create',
  33. ),
  34. );
  35. // Add bundle info but bypass flag_get_flags() as we cannot use it here, as
  36. // it calls entity_get_info().
  37. $result = db_query("SELECT name, title FROM {flag}");
  38. $flag_names = $result->fetchAllKeyed();
  39. foreach ($flag_names as $flag_name => $flag_title) {
  40. $return['flagging']['bundles'][$flag_name] = array(
  41. 'label' => $flag_title,
  42. 'admin' => array(
  43. 'path' => FLAG_ADMIN_PATH . '/manage/%flag',
  44. 'real path' => FLAG_ADMIN_PATH . '/manage/' . $flag_name,
  45. 'bundle argument' => FLAG_ADMIN_PATH_START + 1,
  46. 'access arguments' => array('administer flags'),
  47. ),
  48. );
  49. }
  50. return $return;
  51. }
  52. /**
  53. * Loads a flagging entity.
  54. *
  55. * @param $flagging_id
  56. * The 'flagging_id' database serial column.
  57. * @param $reset
  58. * Whether to reset the DrupalDefaultEntityController cache.
  59. *
  60. * @return
  61. * The entity object, or FALSE if it can't be found.
  62. */
  63. function flagging_load($flagging_id, $reset = FALSE) {
  64. // The flag machine name is loaded in by FlaggingController::buildQuery().
  65. $result = entity_load('flagging', array($flagging_id), array(), $reset);
  66. return reset($result);
  67. }
  68. /**
  69. * Entity API creation callback.
  70. *
  71. * Creates an unsaved flagging object for use with $flag->flag().
  72. *
  73. * @param $values
  74. * An array of values as described by the entity's property info. Only
  75. * 'flag_name' or 'fid' must be specified, since $flag->flag() does the rest.
  76. *
  77. * @return
  78. * An unsaved flagging object containing the property values.
  79. */
  80. function flagging_create($values = array()) {
  81. $flagging = (object) array();
  82. if (!isset($values['flag_name'])) {
  83. if (isset($values['fid'])) {
  84. // Add flag_name, determined from fid.
  85. $flag = flag_get_flag(NULL, $values['fid']);
  86. $values['flag_name'] = $flag->name;
  87. }
  88. }
  89. // Apply the given values.
  90. foreach ($values as $key => $value) {
  91. $flagging->$key = $value;
  92. }
  93. return $flagging;
  94. }
  95. /**
  96. * Saves a flagging entity.
  97. *
  98. * For a new flagging, throws an exception is the flag action is not allowed for
  99. * the given combination of flag, entity, and user.
  100. *
  101. * @param $flagging
  102. * The flagging entity. This may have either flag_name or the flag fid set,
  103. * and may also omit the uid property to use the current user.
  104. *
  105. * @throws Exception
  106. */
  107. function flagging_save($flagging) {
  108. // Get the flag, either way.
  109. if (isset($flagging->flag_name)) {
  110. $flag = flag_get_flag($flagging->flag_name);
  111. }
  112. else {
  113. $flag = flag_get_flag(NULL, $flagging->fid);
  114. }
  115. if (!$flag) {
  116. throw new Exception('Flag not found for flagging entity.');
  117. }
  118. // Fill in properties that may be omitted.
  119. $flagging->fid = $flag->fid;
  120. $flagging->flag_name = $flag->name;
  121. if (!empty($flagging->uid)) {
  122. $account = user_load($flagging->uid);
  123. }
  124. else {
  125. $account = NULL;
  126. }
  127. $result = $flag->flag('flag', $flagging->entity_id, $account, FALSE, $flagging);
  128. if (!$result) {
  129. throw new Exception('Flag action not allowed for given flagging entity properties.');
  130. }
  131. }
  132. // @todo: Implement flagging_view(). Not extremely useful. I already have it.
  133. // @todo: When renaming a flag: Call field_attach_rename_bundle().
  134. // @todo: When creating a flag: Call field_attach_create_bundle().
  135. // @todo: When deleting a flag: Call field_attach_delete_bundle().
  136. // @tood: Discuss: Should flag deleting call flag_reset_flag()? No.
  137. // @todo: flag_reset_flag():
  138. // - it should delete the flaggings.
  139. // - (it has other issues; see http://drupal.org/node/894992.)
  140. // - (is problematic: it might not be possible to delete all data in a single
  141. // page request.)
  142. // @todo: Discuss: Note that almost all functions/identifiers dealing with
  143. // flaggings *aren't* prefixed by "flag_". For example:
  144. // - The menu argument is %flagging, not %flag_flagging.
  145. // - The entity type is "flagging", not "flag_flagging".
  146. // On the one hand this succinct version is readable and nice. On the other
  147. // hand, it isn't very "correct".
  148. /**
  149. * Implements hook_entity_query_alter().
  150. *
  151. * Replaces bundle condition in EntityFieldQuery on flagging entities
  152. * with query condition on [name] field in [flag] table.
  153. *
  154. * @see flag_query_flagging_flag_names_alter()
  155. */
  156. function flag_entity_query_alter(EntityFieldQuery $query) {
  157. $conditions = &$query->entityConditions;
  158. // Alter only flagging queries with bundle conditions.
  159. if (isset($conditions['entity_type']) && $conditions['entity_type']['value'] == 'flagging' && isset($conditions['bundle'])) {
  160. // Add tag to alter query.
  161. $query->addTag('flagging_flag_names');
  162. // Make value and operator of the bundle condition accessible
  163. // in hook_query_TAG_alter.
  164. $query->addMetaData('flag_name_value', $conditions['bundle']['value']);
  165. $query->addMetaData('flag_name_operator', $conditions['bundle']['operator']);
  166. unset($conditions['bundle']);
  167. }
  168. }
  169. /**
  170. * Implements hook_query_TAG_alter() for flagging_flag_names tag.
  171. *
  172. * @see flag_entity_query_alter()
  173. */
  174. function flag_query_flagging_flag_names_alter(QueryAlterableInterface $query) {
  175. // Get value and operator for bundle condition from meta data.
  176. $value = $query->getMetaData('flag_name_value');
  177. $operator = $query->getMetaData('flag_name_operator');
  178. // Join [flag] and [flagging] tables by [fid] and
  179. // apply bundle condition on [flag].[name] field.
  180. $query->join('flag', 'f', 'flagging.fid = f.fid');
  181. $query->condition('f.name', $value, $operator);
  182. }
  183. /**
  184. * Implements hook_menu().
  185. */
  186. function flag_menu() {
  187. $items[FLAG_ADMIN_PATH] = array(
  188. 'title' => 'Flags',
  189. 'page callback' => 'flag_admin_page',
  190. 'access callback' => 'user_access',
  191. 'access arguments' => array('administer flags'),
  192. 'description' => 'Configure flags for marking content with arbitrary information (such as <em>offensive</em> or <em>bookmarked</em>).',
  193. 'file' => 'includes/flag.admin.inc',
  194. );
  195. $items[FLAG_ADMIN_PATH . '/list'] = array(
  196. 'title' => 'List',
  197. 'type' => MENU_DEFAULT_LOCAL_TASK,
  198. 'weight' => -10,
  199. );
  200. $items[FLAG_ADMIN_PATH . '/add'] = array(
  201. 'title' => 'Add flag',
  202. 'page callback' => 'flag_add_page',
  203. 'access callback' => 'user_access',
  204. 'access arguments' => array('administer flags'),
  205. 'file' => 'includes/flag.admin.inc',
  206. 'type' => MENU_LOCAL_ACTION,
  207. 'weight' => 1,
  208. );
  209. $items[FLAG_ADMIN_PATH . '/import'] = array(
  210. 'title' => 'Import',
  211. 'page callback' => 'drupal_get_form',
  212. 'page arguments' => array('flag_import_form'),
  213. 'access arguments' => array('use flag import'),
  214. 'file' => 'includes/flag.export.inc',
  215. 'type' => MENU_LOCAL_ACTION,
  216. 'weight' => 2,
  217. );
  218. $items[FLAG_ADMIN_PATH . '/export'] = array(
  219. 'title' => 'Export',
  220. 'page callback' => 'drupal_get_form',
  221. 'page arguments' => array('flag_export_form'),
  222. 'access arguments' => array('administer flags'),
  223. 'file' => 'includes/flag.export.inc',
  224. 'type' => MENU_LOCAL_ACTION,
  225. 'weight' => 3,
  226. );
  227. $items[FLAG_ADMIN_PATH . '/manage/%flag'] = array(
  228. // Allow for disabled flags.
  229. 'load arguments' => array(TRUE),
  230. 'page callback' => 'drupal_get_form',
  231. 'page arguments' => array('flag_form', FLAG_ADMIN_PATH_START + 1),
  232. 'access callback' => 'user_access',
  233. 'access arguments' => array('administer flags'),
  234. 'file' => 'includes/flag.admin.inc',
  235. 'type' => MENU_LOCAL_TASK,
  236. // Make the flag title the default title for descendant menu items.
  237. 'title callback' => '_flag_menu_title',
  238. 'title arguments' => array(FLAG_ADMIN_PATH_START + 1),
  239. );
  240. $items[FLAG_ADMIN_PATH . '/manage/%flag/edit'] = array(
  241. // Allow for disabled flags.
  242. 'load arguments' => array(TRUE),
  243. 'title' => 'Edit flag',
  244. 'type' => MENU_DEFAULT_LOCAL_TASK,
  245. 'weight' => -10,
  246. );
  247. $items[FLAG_ADMIN_PATH . '/manage/%flag/export'] = array(
  248. 'title' => 'Export',
  249. 'page callback' => 'drupal_get_form',
  250. 'page arguments' => array('flag_export_form', FLAG_ADMIN_PATH_START + 1),
  251. 'access arguments' => array('administer flags'),
  252. 'file' => 'includes/flag.export.inc',
  253. 'type' => MENU_LOCAL_TASK,
  254. 'weight' => 20,
  255. );
  256. $items[FLAG_ADMIN_PATH . '/manage/%flag/delete'] = array(
  257. 'title' => 'Delete flag',
  258. 'page callback' => 'drupal_get_form',
  259. 'page arguments' => array('flag_delete_confirm', FLAG_ADMIN_PATH_START + 1),
  260. 'access callback' => 'user_access',
  261. 'access arguments' => array('administer flags'),
  262. 'file' => 'includes/flag.admin.inc',
  263. 'type' => MENU_CALLBACK,
  264. );
  265. $items[FLAG_ADMIN_PATH . '/manage/%flag/update'] = array(
  266. // Allow for disabled flags.
  267. 'load arguments' => array(TRUE),
  268. 'title' => 'Update',
  269. 'page callback' => 'flag_update_page',
  270. 'page arguments' => array(FLAG_ADMIN_PATH_START + 1),
  271. 'access arguments' => array('administer flags'),
  272. 'file' => 'includes/flag.export.inc',
  273. 'type' => MENU_CALLBACK,
  274. );
  275. $items['flag/%/%flag/%'] = array(
  276. 'title' => 'Flag',
  277. 'page callback' => 'flag_page',
  278. 'page arguments' => array(1, 2, 3),
  279. 'access callback' => 'user_access',
  280. 'access arguments' => array('access content'),
  281. 'file' => 'includes/flag.pages.inc',
  282. 'type' => MENU_CALLBACK,
  283. );
  284. $items['flag/confirm/%/%flag/%'] = array(
  285. 'title' => 'Flag confirm',
  286. 'page callback' => 'drupal_get_form',
  287. 'page arguments' => array('flag_confirm', 2, 3, 4),
  288. 'access callback' => 'user_access',
  289. 'access arguments' => array('access content'),
  290. 'file' => 'includes/flag.pages.inc',
  291. 'type' => MENU_CALLBACK,
  292. );
  293. return $items;
  294. }
  295. /**
  296. * Implements hook_admin_menu_map().
  297. */
  298. function flag_admin_menu_map() {
  299. if (!user_access('administer flags')) {
  300. return;
  301. }
  302. $map = array();
  303. $map[FLAG_ADMIN_PATH . '/manage/%flag'] = array(
  304. 'parent' => FLAG_ADMIN_PATH,
  305. 'arguments' => array(
  306. array(
  307. '%flag' => array_keys(flag_get_flags()),
  308. ),
  309. ),
  310. );
  311. return $map;
  312. }
  313. /**
  314. * Menu loader for '%flag' arguments.
  315. *
  316. * @param $flag_name
  317. * The machine name of the flag.
  318. * @param $include_disabled
  319. * (optional) Whether to return a disabled flag too. Normally only enabled
  320. * flags are returned. Some menu items operate on disabled flags and in this
  321. * case you need to turn on this switch by doing:
  322. * @code
  323. * 'load arguments' => array(TRUE)
  324. * @endcode
  325. * in your hook_menu().
  326. *
  327. * @return
  328. * Either the flag object, or FALSE if none was found.
  329. */
  330. function flag_load($flag_name, $include_disabled = FALSE) {
  331. if (($flag = flag_get_flag($flag_name))) {
  332. return $flag;
  333. }
  334. else {
  335. // No enabled flag was found. Search among the disabled ones.
  336. if ($include_disabled) {
  337. $default_flags = flag_get_default_flags(TRUE);
  338. if (isset($default_flags[$flag_name])) {
  339. return $default_flags[$flag_name];
  340. }
  341. }
  342. }
  343. // A menu loader has to return FALSE (not NULL) when no object is found.
  344. return FALSE;
  345. }
  346. /**
  347. * Menu title callback.
  348. */
  349. function _flag_menu_title($flag) {
  350. // The following conditional it to handle a D7 bug (@todo: link).
  351. return $flag ? $flag->get_title() : '';
  352. }
  353. /**
  354. * Implements hook_help().
  355. */
  356. function flag_help($path, $arg) {
  357. switch ($path) {
  358. case FLAG_ADMIN_PATH:
  359. $output = '<p>' . t('This page lists all the <em>flags</em> that are currently defined on this system.') . '</p>';
  360. return $output;
  361. case FLAG_ADMIN_PATH . '/add':
  362. $output = '<p>' . t('Select the type of flag to create. An individual flag can only affect one type of object. This cannot be changed once the flag is created.') . '</p>';
  363. return $output;
  364. case FLAG_ADMIN_PATH . '/manage/%/fields':
  365. // Get the existing link types that provide a flagging form.
  366. $link_types = flag_get_link_types();
  367. $form_link_types = array();
  368. foreach (flag_get_link_types() as $link_type) {
  369. if ($link_type['provides form']) {
  370. $form_link_types[] = '<em>' . $link_type['title'] . '</em>';
  371. }
  372. }
  373. // Get the flag for which we're managing fields.
  374. $flag = menu_get_object('flag', FLAG_ADMIN_PATH_START + 1);
  375. // Common text.
  376. $output = '<p>' . t('Flags can have fields added to them. For example, a "Spam" flag could have a <em>Reason</em> field where a user could type in why he believes the item flagged is spam. A "Bookmarks" flag could have a <em>Folder</em> field into which a user could arrange her bookmarks.') . '</p>';
  377. $output .= '<p>' . t('On this page you can add fields to flags, delete them, and otherwise manage them.') . '</p>';
  378. // Three cases:
  379. if ($flag->link_type == 'form') {
  380. // Case 1: the current link type is the flagging form. Don't tell the
  381. // user anything extra, all is fine.
  382. }
  383. elseif ($link_types[$flag->link_type]['provides form']) {
  384. // Case 2: the current link type shows the form for creation of the
  385. // flagging, but it not the flagging form. Tell the user they can't edit
  386. // existing flagging fields.
  387. $output .= t("Field values may be edited when flaggings are created because this flag's link type shows a form for the flagging. However, to edit field values on existing flaggings, you will need to set your flag to use the <em>Flagging form</em> link type. This is provided by the <em><a href='!flagging-form-url'>Flagging Form</a></em> module.", array(
  388. '!flagging-form-url' => 'http://drupal.org/project/flagging_form',
  389. ));
  390. if (!module_exists('flagging_form')) {
  391. $output .= ' <span class="warning">'
  392. . t("You do not currently have this module enabled.")
  393. . '</span>';
  394. }
  395. $output .= '</p>';
  396. }
  397. else {
  398. // Case 3: the current link type does not allow access to the flagging
  399. // form. Tell the user they should change it.
  400. $output .= '<p class="warning">' . t("To allow users to enter values for fields you will need to <a href='!form-link-type-url'>set your flag</a> to use one of the following link types which allow users to access the flagging form: !link-types-list. (In case a form isn't used, the fields are assigned their default values.)", array(
  401. '!form-link-type-url' => url('admin/structure/flags/manage/' . $flag->name, array('fragment' => 'edit-link-type')),
  402. // The list of labels from link types. These are all defined in code
  403. // in hook_flag_link_type_info() and therefore safe to output raw.
  404. '!link-types-list' => implode(', ', $form_link_types),
  405. )) . '</p>';
  406. $output .= '<p>' . t("Additionally, to edit field values on existing flaggings, you will need to set your flag to use the Flagging form link type. This is provided by the <em><a href='!flagging-form-url'>Flagging Form</a></em> module.", array(
  407. '!flagging-form-url' => 'http://drupal.org/project/flagging_form',
  408. ));
  409. if (!module_exists('flagging_form')) {
  410. $output .= ' <span class="warning">'
  411. . t("You do not currently have this module enabled.")
  412. . '</span>';
  413. }
  414. $output .= '</p>';
  415. }
  416. return $output;
  417. }
  418. }
  419. /**
  420. * Implements hook_init().
  421. */
  422. function flag_init() {
  423. module_load_include('inc', 'flag', 'includes/flag.actions');
  424. }
  425. /**
  426. * Implements hook_hook_info().
  427. */
  428. function flag_hook_info() {
  429. $hooks['flag_type_info'] = array(
  430. 'group' => 'flag',
  431. );
  432. $hooks['flag_type_info_alter'] = array(
  433. 'group' => 'flag',
  434. );
  435. $hooks['flag_link_type_info'] = array(
  436. 'group' => 'flag',
  437. );
  438. $hooks['flag_link_type_info_alter'] = array(
  439. 'group' => 'flag',
  440. );
  441. return $hooks;
  442. }
  443. /**
  444. * Get a flag type definition.
  445. *
  446. * @param $entity_type
  447. * (optional) The entity type to get the definition for, or NULL to return
  448. * all flag types.
  449. *
  450. * @return
  451. * The flag type definition array.
  452. *
  453. * @see hook_flag_type_info()
  454. */
  455. function flag_fetch_definition($entity_type = NULL) {
  456. $definitions = &drupal_static(__FUNCTION__);
  457. if (!isset($definitions)) {
  458. if ($cache = cache_get('flag_type_info')) {
  459. $definitions = $cache->data;
  460. }
  461. else {
  462. $definitions = module_invoke_all('flag_type_info');
  463. drupal_alter('flag_type_info', $definitions);
  464. cache_set('flag_type_info', $definitions);
  465. }
  466. }
  467. if (isset($entity_type)) {
  468. if (isset($definitions[$entity_type])) {
  469. return $definitions[$entity_type];
  470. }
  471. }
  472. else {
  473. return $definitions;
  474. }
  475. }
  476. /**
  477. * Returns all flag types defined on the system.
  478. *
  479. * @return
  480. * An array of flag type names.
  481. */
  482. function flag_get_types() {
  483. $types = &drupal_static(__FUNCTION__);
  484. if (!isset($types)) {
  485. $types = array_keys(flag_fetch_definition());
  486. }
  487. return $types;
  488. }
  489. /**
  490. * Instantiates a new flag handler.
  491. *
  492. * A flag handler is more commonly know as "a flag". A factory method usually
  493. * populates this empty flag with settings loaded from the database.
  494. *
  495. * @param $entity_type
  496. * The entity type to create a flag handler for. This may be FALSE if the
  497. * entity type property could not be found in the flag configuration data.
  498. *
  499. * @return
  500. * A flag handler object. This may be the special class flag_broken is there is
  501. * a problem with the flag.
  502. */
  503. function flag_create_handler($entity_type) {
  504. $definition = flag_fetch_definition($entity_type);
  505. if (isset($definition) && class_exists($definition['handler'])) {
  506. $handler = new $definition['handler']();
  507. }
  508. else {
  509. $handler = new flag_broken();
  510. }
  511. $handler->entity_type = $entity_type;
  512. $handler->construct();
  513. return $handler;
  514. }
  515. /**
  516. * Implements hook_permission().
  517. */
  518. function flag_permission() {
  519. $permissions = array(
  520. 'administer flags' => array(
  521. 'title' => t('Administer flags'),
  522. 'description' => t('Create and edit site-wide flags.'),
  523. ),
  524. 'use flag import' => array(
  525. 'title' => t('Use flag importer'),
  526. 'description' => t('Access the flag import functionality.'),
  527. 'restrict access' => TRUE,
  528. ),
  529. );
  530. // Reset static cache to ensure all flag permissions are available.
  531. drupal_static_reset('flag_get_flags');
  532. $flags = flag_get_flags();
  533. // Provide flag and unflag permissions for each flag.
  534. foreach ($flags as $flag_name => $flag) {
  535. $permissions += $flag->get_permissions();
  536. }
  537. return $permissions;
  538. }
  539. /**
  540. * Implements hook_form_FORM_ID_alter(): user_admin_permissions.
  541. *
  542. * Disable permission on the permissions form that don't make sense for
  543. * anonymous users when Session API module is not enabled.
  544. */
  545. function flag_form_user_admin_permissions_alter(&$form, &$form_state, $form_id) {
  546. if (!module_exists('session_api')) {
  547. $flags = flag_get_flags();
  548. // Disable flag and unflag permission checkboxes for anonymous users.
  549. foreach ($flags as $flag_name => $flag) {
  550. $form['checkboxes'][DRUPAL_ANONYMOUS_RID]["flag $flag_name"]['#disabled'] = TRUE;
  551. $form['checkboxes'][DRUPAL_ANONYMOUS_RID]["unflag $flag_name"]['#disabled'] = TRUE;
  552. }
  553. }
  554. }
  555. /**
  556. * Implements hook_flag_link().
  557. */
  558. function flag_flag_link($flag, $action, $entity_id) {
  559. $token = flag_get_token($entity_id);
  560. return array(
  561. 'href' => 'flag/' . ($flag->link_type == 'confirm' ? 'confirm/' : '') . "$action/$flag->name/$entity_id",
  562. 'query' => drupal_get_destination() + ($flag->link_type == 'confirm' ? array() : array('token' => $token)),
  563. );
  564. }
  565. /**
  566. * Implements hook_field_extra_fields().
  567. */
  568. function flag_field_extra_fields() {
  569. $extra = array();
  570. $flags = flag_get_flags();
  571. foreach ($flags as $name => $flag) {
  572. // Skip flags that aren't on entities.
  573. if (!($flag instanceof flag_entity)) {
  574. continue;
  575. }
  576. $applicable_bundles = $flag->types;
  577. // If the list of bundles is empty, it indicates all bundles apply.
  578. if (empty($applicable_bundles)) {
  579. $entity_info = entity_get_info($flag->entity_type);
  580. $applicable_bundles = array_keys($entity_info['bundles']);
  581. }
  582. foreach ($applicable_bundles as $bundle_name) {
  583. if ($flag->show_on_form) {
  584. $extra[$flag->entity_type][$bundle_name]['form']['flag'] = array(
  585. 'label' => t('Flags'),
  586. 'description' => t('Checkboxes for toggling flags'),
  587. 'weight' => 10,
  588. );
  589. }
  590. if ($flag->show_as_field) {
  591. $extra[$flag->entity_type][$bundle_name]['display']['flag_' . $name] = array(
  592. // It would be nicer to use % as the placeholder, but the label is
  593. // run through check_plain() by field_ui_display_overview_form()
  594. // (arguably incorrectly; see http://drupal.org/node/1991292).
  595. 'label' => t('Flag: @title', array(
  596. '@title' => $flag->title,
  597. )),
  598. 'description' => t('Individual flag link'),
  599. 'weight' => 10,
  600. );
  601. }
  602. }
  603. }
  604. return $extra;
  605. }
  606. /**
  607. * Implements hook_form_FORM_ID_alter(): node_type_form.
  608. */
  609. function flag_form_node_type_form_alter(&$form, &$form_state, $form_id) {
  610. global $user;
  611. $flags = flag_get_flags('node', $form['#node_type']->type, $user);
  612. foreach ($flags as $flag) {
  613. if ($flag->show_on_form) {
  614. // To be able to process node tokens in flag labels, we create a fake
  615. // node and store it in the flag's cache for replace_tokens() to find,
  616. // with a fake ID.
  617. $flag->remember_entity('fake', (object) array(
  618. 'nid' => NULL,
  619. 'type' => $form['#node_type']->type,
  620. 'title' => '',
  621. ));
  622. $var = 'flag_' . $flag->name . '_default';
  623. $form['workflow']['flag'][$var] = array(
  624. '#type' => 'checkbox',
  625. '#title' => $flag->get_label('flag_short', 'fake'),
  626. '#default_value' => variable_get($var . '_' . $form['#node_type']->type, 0),
  627. '#return_value' => 1,
  628. );
  629. }
  630. }
  631. if (isset($form['workflow']['flag'])) {
  632. $form['workflow']['flag'] += array(
  633. '#type' => 'item',
  634. '#title' => t('Default flags'),
  635. '#description' => t('Above are the <a href="@flag-url">flags</a> you elected to show on the node editing form. You may specify their initial state here.', array('@flag-url' => url(FLAG_ADMIN_PATH))),
  636. // Make the spacing a bit more compact:
  637. '#prefix' => '<div class="form-checkboxes">',
  638. '#suffix' => '</div>',
  639. );
  640. }
  641. }
  642. /**
  643. * Implements hook_field_attach_form().
  644. *
  645. * Handles the 'show_on_form' flag option.
  646. *
  647. * Warning: will not work on entity types that are not fieldable, as this relies
  648. * on a field module hook.
  649. *
  650. * @see flag_field_attach_submit()
  651. */
  652. function flag_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  653. list($id) = entity_extract_ids($entity_type, $entity);
  654. // Some modules are being stupid here. Commerce!
  655. if (empty($id)) {
  656. $id = NULL;
  657. }
  658. // Keep track of whether the entity is new or not, as we're about to fiddle
  659. // with the entity id for the flag's entity cache.
  660. $is_existing_entity = !empty($id);
  661. // Get all possible flags for this entity type.
  662. $flags = flag_get_flags($entity_type);
  663. // Filter out flags which need to be included on the node form.
  664. $flags_in_form = 0;
  665. $flags_visible = 0;
  666. foreach ($flags as $flag) {
  667. if (!$flag->show_on_form) {
  668. continue;
  669. }
  670. // Get the flag status.
  671. if ($is_existing_entity) {
  672. $flag_status = $flag->is_flagged($id);
  673. }
  674. else {
  675. // We don't have per-bundle defaults on general entities yet: default
  676. // status is just unflagged.
  677. $flag_status = FALSE;
  678. // Apply the per-bundle defaults for nodes.
  679. if ($entity_type == 'node') {
  680. $node_type = $entity->type;
  681. $flag_status = variable_get('flag_' . $flag->name . '_default_' . $node_type, 0);
  682. }
  683. // For a new, unsaved entity, make a dummy entity ID so that the flag
  684. // handler can remember the entity. This allows access to the flag to be
  685. // correctly handled in node and comment preview.
  686. $id = 'new';
  687. $flag->remember_entity($id, $entity);
  688. }
  689. // If the flag is not global and the user doesn't have access, skip it.
  690. // Global flags have their value set even if the user doesn't have access
  691. // to it, similar to the way "published" and "promote" keep the default
  692. // values even if the user doesn't have "administer nodes" permission.
  693. // Furthermore, a global flag is set to its default value on new nodes
  694. // even if the user creating the node doesn't have access to the flag.
  695. global $user;
  696. $access = $flag->access($id, $flag_status ? 'unflag' : 'flag');
  697. if (!$access && !$flag->global) {
  698. continue;
  699. }
  700. $form['flag'][$flag->name] = array(
  701. '#type' => 'checkbox',
  702. '#title' => $flag->get_label('flag_short', $id),
  703. '#description' => $flag->get_label('flag_long', $id),
  704. '#default_value' => $flag_status,
  705. '#return_value' => 1,
  706. // Used by our drupalSetSummary() on vertical tabs.
  707. '#attributes' => array('title' => $flag->get_title()),
  708. );
  709. // If the user does not have access to the flag, set as a value.
  710. if (!$access) {
  711. $form['flag'][$flag->name]['#type'] = 'value';
  712. $form['flag'][$flag->name]['#value'] = $flag_status;
  713. }
  714. else {
  715. $flags_visible++;
  716. }
  717. $flags_in_form++;
  718. }
  719. if ($flags_in_form) {
  720. $form['flag'] += array(
  721. '#weight' => 1,
  722. '#tree' => TRUE,
  723. );
  724. }
  725. if ($flags_visible) {
  726. $form['flag'] += array(
  727. '#type' => 'fieldset',
  728. '#title' => t('Flags'),
  729. '#collapsible' => TRUE,
  730. );
  731. if ($entity_type == 'node') {
  732. // Turn the fieldset into a vertical tab.
  733. $form['flag'] += array(
  734. '#group' => 'additional_settings',
  735. '#attributes' => array('class' => array('flag-fieldset')),
  736. '#attached' => array(
  737. 'js' => array(
  738. 'vertical-tabs' => drupal_get_path('module', 'flag') . '/theme/flag-admin.js',
  739. ),
  740. ),
  741. );
  742. }
  743. }
  744. }
  745. /**
  746. * Implements hook_field_attach_submit().
  747. *
  748. * @see flag_field_attach_form()
  749. */
  750. function flag_field_attach_submit($entity_type, $entity, $form, &$form_state) {
  751. // This is invoked for each flag_field_attach_form(), but possibly more than
  752. // once for a particular form in the case that a form is showing multiple
  753. // entities (field collection, inline entity form). Hence we can't simply
  754. // assume our submitted form values are in $form_state['values']['flag'].
  755. if (isset($form['flag'])) {
  756. $parents = $form['flag']['#parents'];
  757. $flag_values = drupal_array_get_nested_value($form_state['values'], $parents);
  758. // Put the form values in the entity so flag_field_attach_save() can find
  759. // them. We can't call flag() here as new entities have no id yet.
  760. $entity->flag = $flag_values;
  761. }
  762. }
  763. /**
  764. * Implements hook_field_attach_insert().
  765. */
  766. function flag_field_attach_insert($entity_type, $entity) {
  767. if (isset($entity->flag)) {
  768. flag_field_attach_save($entity_type, $entity);
  769. }
  770. }
  771. /**
  772. * Implements hook_field_attach_update().
  773. */
  774. function flag_field_attach_update($entity_type, $entity) {
  775. if (isset($entity->flag)) {
  776. flag_field_attach_save($entity_type, $entity);
  777. }
  778. }
  779. /**
  780. * Shared saving routine between flag_field_attach_insert/update().
  781. *
  782. * @see flag_field_attach_form()
  783. */
  784. function flag_field_attach_save($entity_type, $entity) {
  785. list($id) = entity_extract_ids($entity_type, $entity);
  786. // Get the flag values we stashed in the entity in flag_field_attach_submit().
  787. foreach ($entity->flag as $flag_name => $state) {
  788. flag($state ? 'flag' : 'unflag', $flag_name, $id);
  789. }
  790. }
  791. /*
  792. * Implements hook_contextual_links_view_alter().
  793. */
  794. function flag_contextual_links_view_alter(&$element, $items) {
  795. if (isset($element['#element']['#entity_type'])) {
  796. $entity_type = $element['#element']['#entity_type'];
  797. // Get the entity out of the element. This requires a bit of legwork.
  798. if (isset($element['#element']['#entity'])) {
  799. // EntityAPI entities will all have the entity in the same place.
  800. $entity = $element['#element']['#entity'];
  801. }
  802. elseif (isset($element['#element']['#' . $entity_type])) {
  803. // Node module at least puts it here.
  804. $entity = $element['#element']['#' . $entity_type];
  805. }
  806. else {
  807. // Give up.
  808. return;
  809. }
  810. // Get all possible flags for this entity type.
  811. $flags = flag_get_flags($entity_type);
  812. foreach ($flags as $name => $flag) {
  813. if (!$flag->show_contextual_link) {
  814. continue;
  815. }
  816. list($entity_id) = entity_extract_ids($entity_type, $entity);
  817. if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
  818. // User has no permission to use this flag or flag does not apply to
  819. // this object. The link is not skipped if the user has "flag" access
  820. // but not "unflag" access (this way the unflag denied message is
  821. // shown).
  822. continue;
  823. }
  824. $element['#links']['flag-' . $name] = array(
  825. 'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
  826. 'html' => TRUE,
  827. );
  828. }
  829. }
  830. }
  831. /**
  832. * Implements hook_entity_view().
  833. *
  834. * Handles the 'show_in_links' and 'show_as_field' flag options.
  835. *
  836. * Note this is broken for taxonomy terms for version of Drupal core < 7.17.
  837. */
  838. function flag_entity_view($entity, $type, $view_mode, $langcode) {
  839. // Get all possible flags for this entity type.
  840. $flags = flag_get_flags($type);
  841. foreach ($flags as $flag) {
  842. // Check if the flag outputs on entity view.
  843. if (!($flag->show_as_field || $flag->shows_in_entity_links($view_mode))) {
  844. // Flag is not configured to output on entity view, so skip it to save on
  845. // calls to access checks.
  846. continue;
  847. }
  848. $entity_id = $flag->get_entity_id($entity);
  849. // For a new, unsaved entity, make a dummy entity ID so that the flag
  850. // handler can remember the entity. This allows access to the flag to be
  851. // correctly handled in node and comment preview.
  852. if (is_null($entity_id)) {
  853. $entity_id = 'new';
  854. }
  855. $flag->remember_entity($entity_id, $entity);
  856. if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
  857. // User has no permission to use this flag or flag does not apply to this
  858. // entity. The link is not skipped if the user has "flag" access but
  859. // not "unflag" access (this way the unflag denied message is shown).
  860. continue;
  861. }
  862. // We're good to go. Output the flag in the appropriate manner(s).
  863. // The old-style entity links output.
  864. if ($flag->shows_in_entity_links($view_mode)) {
  865. // The flag links are actually fully rendered theme functions.
  866. // The HTML attribute is set to TRUE to allow whatever the themer desires.
  867. $links['flag-' . $flag->name] = array(
  868. 'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
  869. 'html' => TRUE,
  870. );
  871. }
  872. // The pseudofield output.
  873. if ($flag->show_as_field) {
  874. $entity->content['flag_' . $flag->name] = array(
  875. '#markup' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, array('needs_wrapping_element' => TRUE)),
  876. );
  877. }
  878. }
  879. // If any links were made, add them to the entity's links array.
  880. if (isset($links)) {
  881. $entity->content['links']['flag'] = array(
  882. '#theme' => 'links',
  883. '#links' => $links,
  884. '#attributes' => array('class' => array('links', 'inline')),
  885. );
  886. }
  887. }
  888. /**
  889. * Implements hook_node_insert().
  890. */
  891. function flag_node_insert($node) {
  892. flag_node_save($node);
  893. }
  894. /**
  895. * Implements hook_node_update().
  896. */
  897. function flag_node_update($node) {
  898. flag_node_save($node);
  899. }
  900. /**
  901. * Shared saving routine between flag_node_insert() and flag_node_update().
  902. */
  903. function flag_node_save($node) {
  904. // Response to the flag checkboxes added to the form in flag_form_alter().
  905. $remembered = FALSE;
  906. if (isset($node->flag)) {
  907. foreach ($node->flag as $name => $state) {
  908. $flag = flag_get_flag($name);
  909. // Flagging may trigger actions. We want actions to get the current
  910. // node, not a stale database-loaded one:
  911. if (!$remembered) {
  912. $flag->remember_entity($node->nid, $node);
  913. // Actions may modify a node, and we don't want to overwrite this
  914. // modification:
  915. $remembered = TRUE;
  916. }
  917. $action = $state ? 'flag' : 'unflag';
  918. // Pass TRUE for $skip_permission_check so that flags that have been
  919. // passed through as hidden form values are saved.
  920. $flag->flag($action, $node->nid, NULL, TRUE);
  921. }
  922. }
  923. }
  924. /**
  925. * Implements hook_entity_delete().
  926. */
  927. function flag_entity_delete($entity, $type) {
  928. // Node and user flags handle things through the entity type delete hooks.
  929. // @todo: make this configurable in the flag type definition?
  930. if ($type == 'node' || $type == 'user') {
  931. return;
  932. }
  933. list($id) = entity_extract_ids($type, $entity);
  934. _flag_entity_delete($type, $id);
  935. }
  936. /**
  937. * Implements hook_node_delete().
  938. */
  939. function flag_node_delete($node) {
  940. foreach (flag_get_flags('node') as $flag) {
  941. // If the flag is being tracked by translation set and the node is part
  942. // of a translation set, don't delete the flagging record.
  943. // Instead, data will be updated in hook_node_translation_change(), below.
  944. if (!$flag->i18n || empty($node->tnid)) {
  945. _flag_entity_delete('node', $node->nid, $flag->fid);
  946. }
  947. }
  948. }
  949. /**
  950. * Implements hook_node_translation_change().
  951. *
  952. * (Hook provided by translation_helpers module.)
  953. */
  954. function flag_node_translation_change($node) {
  955. if (isset($node->translation_change)) {
  956. // If there is only one node remaining, track by nid rather than tnid.
  957. // Otherwise, use the new tnid.
  958. $entity_id = $node->translation_change['new_tnid'] == 0 ? $node->translation_change['remaining_nid'] : $node->translation_change['new_tnid'];
  959. foreach (flag_get_flags('node') as $flag) {
  960. if ($flag->i18n) {
  961. db_update('flagging')->fields(array('entity_id' => $entity_id))
  962. ->condition('fid', $flag->fid)
  963. ->condition('entity_id', $node->translation_change['old_tnid'])
  964. ->execute();
  965. db_update('flag_counts')->fields(array('entity_id' => $entity_id))
  966. ->condition('fid', $flag->fid)
  967. ->condition('entity_id', $node->translation_change['old_tnid'])
  968. ->execute();
  969. }
  970. }
  971. }
  972. }
  973. /**
  974. * Deletes flagging records for the entity.
  975. *
  976. * @param $entity_type
  977. * The type of the entity being deleted; e.g. 'node' or 'comment'.
  978. * @param $entity_id
  979. * The ID of the entity being deleted.
  980. * @param $fid
  981. * The flag id
  982. */
  983. function _flag_entity_delete($entity_type, $entity_id, $fid = NULL) {
  984. $query_content = db_delete('flagging')
  985. ->condition('entity_type', $entity_type)
  986. ->condition('entity_id', $entity_id);
  987. $query_counts = db_delete('flag_counts')
  988. ->condition('entity_type', $entity_type)
  989. ->condition('entity_id', $entity_id);
  990. if (isset($fid)) {
  991. $query_content->condition('fid', $fid);
  992. $query_counts->condition('fid', $fid);
  993. }
  994. $query_content->execute();
  995. $query_counts->execute();
  996. }
  997. /**
  998. * Implements hook_user_login().
  999. */
  1000. function flag_user_login(&$edit, &$account) {
  1001. // Migrate anonymous flags to this user's account.
  1002. if (module_exists('session_api') && ($sid = flag_get_sid(0))) {
  1003. // Get a list of flagging IDs that will be moved over.
  1004. $duplicate_flaggings = array();
  1005. $flaggings = db_select('flagging', 'fc')
  1006. ->fields('fc', array('flagging_id', 'fid', 'entity_id'))
  1007. ->condition('uid', 0)
  1008. ->condition('sid', $sid)
  1009. ->execute()
  1010. ->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
  1011. // Convert anonymous flaggings to their authenticated account.
  1012. foreach ($flaggings as $flagging_id => $flagging) {
  1013. // Each update is wrapped in a try block to prevent unique key errors.
  1014. // Any duplicate object that was flagged as anonoymous is deleted in the
  1015. // subsequent db_delete() call.
  1016. try {
  1017. db_update('flagging')
  1018. ->fields(array(
  1019. 'uid' => $account->uid,
  1020. 'sid' => 0,
  1021. ))
  1022. ->condition('flagging_id', $flagging_id)
  1023. ->execute();
  1024. }
  1025. catch (Exception $e) {
  1026. $duplicate_flaggings[$flagging_id] = $flagging;
  1027. }
  1028. }
  1029. // Delete any remaining flags this user had as an anonymous user. We use the
  1030. // proper unflag action here to make sure the count gets decremented again
  1031. // and so that other modules can clean up their tables if needed.
  1032. $anonymous_user = drupal_anonymous_user();
  1033. foreach ($duplicate_flaggings as $flagging_id => $flagging) {
  1034. $flag = flag_get_flag(NULL, $flagging['fid']);
  1035. $flag->flag('unflag', $flagging['entity_id'], $anonymous_user, TRUE);
  1036. }
  1037. // Clean up anonymous cookies.
  1038. FlagCookieStorage::drop();
  1039. }
  1040. }
  1041. /**
  1042. * Implements hook_user_cancel().
  1043. */
  1044. function flag_user_cancel($edit, $account, $method) {
  1045. flag_user_account_removal($account);
  1046. }
  1047. /**
  1048. * Implements hook_user_delete().
  1049. */
  1050. function flag_user_delete($account) {
  1051. flag_user_account_removal($account);
  1052. }
  1053. /**
  1054. * Shared helper for user account cancellation or deletion.
  1055. */
  1056. function flag_user_account_removal($account) {
  1057. // Remove flags by this user.
  1058. $query = db_select('flagging', 'fc');
  1059. $query->leftJoin('flag_counts', 'c', 'fc.entity_id = c.entity_id AND fc.entity_type = c.entity_type AND fc.fid = c.fid');
  1060. $result = $query
  1061. ->fields('fc', array('fid', 'entity_id'))
  1062. ->fields('c', array('count'))
  1063. ->condition('fc.uid', $account->uid)
  1064. ->execute();
  1065. foreach ($result as $flag_data) {
  1066. // Only decrement the flag count table if it's greater than 1.
  1067. if ($flag_data->count > 0) {
  1068. $flag_data->count--;
  1069. db_update('flag_counts')
  1070. ->fields(array(
  1071. 'count' => $flag_data->count,
  1072. ))
  1073. ->condition('fid', $flag_data->fid)
  1074. ->condition('entity_id', $flag_data->entity_id)
  1075. ->execute();
  1076. }
  1077. elseif ($flag_data->count == 0) {
  1078. db_delete('flag_counts')
  1079. ->condition('fid', $flag_data->fid)
  1080. ->condition('entity_id', $flag_data->entity_id)
  1081. ->execute();
  1082. }
  1083. }
  1084. db_delete('flagging')
  1085. ->condition('uid', $account->uid)
  1086. ->execute();
  1087. // Remove flags that have been done to this user.
  1088. _flag_entity_delete('user', $account->uid);
  1089. }
  1090. /**
  1091. * Implements hook_user_view().
  1092. */
  1093. function flag_user_view($account, $view_mode) {
  1094. $flags = flag_get_flags('user');
  1095. $flag_items = array();
  1096. foreach ($flags as $flag) {
  1097. if (!$flag->access($account->uid)) {
  1098. // User has no permission to use this flag.
  1099. continue;
  1100. }
  1101. if (!$flag->show_on_profile) {
  1102. // Flag not set to appear on profile.
  1103. continue;
  1104. }
  1105. $flag_items[$flag->name] = array(
  1106. '#type' => 'user_profile_item',
  1107. '#title' => $flag->get_title($account->uid),
  1108. '#markup' => $flag->theme($flag->is_flagged($account->uid) ? 'unflag' : 'flag', $account->uid),
  1109. '#attributes' => array('class' => array('flag-profile-' . $flag->name)),
  1110. );
  1111. }
  1112. if (!empty($flag_items)) {
  1113. $account->content['flags'] = $flag_items;
  1114. $account->content['flags'] += array(
  1115. '#type' => 'user_profile_category',
  1116. '#title' => t('Actions'),
  1117. '#attributes' => array('class' => array('flag-profile')),
  1118. );
  1119. }
  1120. }
  1121. /**
  1122. * Implements hook_session_api_cleanup().
  1123. *
  1124. * Clear out anonymous user flaggings during Session API cleanup.
  1125. */
  1126. function flag_session_api_cleanup($arg = 'run') {
  1127. // Session API 1.1 version:
  1128. if ($arg == 'run') {
  1129. $query = db_select('flagging', 'fc');
  1130. $query->leftJoin('session_api', 's', 'fc.sid = s.sid');
  1131. $result = $query
  1132. ->fields('fc', array('sid'))
  1133. ->condition('fc.sid', 0, '<>')
  1134. ->isNull('s.sid')
  1135. ->execute();
  1136. foreach ($result as $row) {
  1137. db_delete('flagging')
  1138. ->condition('sid', $row->sid)
  1139. ->execute();
  1140. }
  1141. }
  1142. // Session API 1.2+ version.
  1143. elseif (is_array($arg)) {
  1144. $outdated_sids = $arg;
  1145. db_delete('flagging')->condition('sid', $outdated_sids, 'IN')->execute();
  1146. }
  1147. }
  1148. /**
  1149. * Implements hook_field_attach_delete_bundle().
  1150. *
  1151. * Delete any flags' applicability to the deleted bundle.
  1152. */
  1153. function flag_field_attach_delete_bundle($entity_type, $bundle, $instances) {
  1154. // This query can't use db_delete() because that doesn't support a
  1155. // subquery: see http://drupal.org/node/1267508.
  1156. db_query("DELETE FROM {flag_types} WHERE type = :bundle AND fid IN (SELECT fid FROM {flag} WHERE entity_type = :entity_type)", array(
  1157. ':bundle' => $bundle,
  1158. ':entity_type' => $entity_type,
  1159. ));
  1160. }
  1161. /**
  1162. * Flags or unflags an item.
  1163. *
  1164. * @param $action
  1165. * Either 'flag' or 'unflag'.
  1166. * @param $flag_name
  1167. * The name of the flag to use.
  1168. * @param $entity_id
  1169. * The ID of the item to flag or unflag.
  1170. * @param $account
  1171. * (optional) The user on whose behalf to flag. Omit for the current user.
  1172. * @param permissions_check
  1173. * (optional) A boolean indicating whether to skip permissions.
  1174. *
  1175. * @return
  1176. * FALSE if some error occured (e.g., user has no permission, flag isn't
  1177. * applicable to the item, etc.), TRUE otherwise.
  1178. */
  1179. function flag($action, $flag_name, $entity_id, $account = NULL, $permissions_check = FALSE) {
  1180. if (!($flag = flag_get_flag($flag_name))) {
  1181. // Flag does not exist.
  1182. return FALSE;
  1183. }
  1184. return $flag->flag($action, $entity_id, $account, $permissions_check);
  1185. }
  1186. /**
  1187. * Implements hook_flag_flag().
  1188. */
  1189. function flag_flag_flag($flag, $entity_id, $account, $flagging) {
  1190. if (module_exists('trigger')) {
  1191. flag_flag_trigger('flag', $flag, $entity_id, $account, $flagging);
  1192. }
  1193. }
  1194. /**
  1195. * Implements hook_flag_unflag().
  1196. */
  1197. function flag_flag_unflag($flag, $entity_id, $account, $flagging) {
  1198. if (module_exists('trigger')) {
  1199. flag_flag_trigger('unflag', $flag, $entity_id, $account, $flagging);
  1200. }
  1201. }
  1202. /**
  1203. * Trigger actions if any are available. Helper for hook_flag_(un)flag().
  1204. *
  1205. * @param $op
  1206. * The operation being performed: one of 'flag' or 'unflag'.
  1207. * @param $flag
  1208. * The flag object.
  1209. * @param $entity_id
  1210. * The id of the entity the flag is on.
  1211. * @param $account
  1212. * The user account performing the action.
  1213. * @param $flagging_id
  1214. * The flagging entity.
  1215. */
  1216. function flag_flag_trigger($action, $flag, $entity_id, $account, $flagging) {
  1217. $context['hook'] = 'flag';
  1218. $context['account'] = $account;
  1219. $context['flag'] = $flag;
  1220. $context['op'] = $action;
  1221. // We add to the $context all the objects we know about:
  1222. $context = array_merge($flag->get_relevant_action_objects($entity_id), $context);
  1223. // The primary object the actions work on.
  1224. $object = $flag->fetch_entity($entity_id);
  1225. // Generic "all flags" actions.
  1226. foreach (trigger_get_assigned_actions('flag_' . $action) as $aid => $action_info) {
  1227. // The 'if ($aid)' is a safeguard against
  1228. // http://drupal.org/node/271460#comment-886564
  1229. if ($aid) {
  1230. actions_do($aid, $object, $context);
  1231. }
  1232. }
  1233. // Actions specifically for this flag.
  1234. foreach (trigger_get_assigned_actions('flag_' . $action . '_' . $flag->name) as $aid => $action_info) {
  1235. if ($aid) {
  1236. actions_do($aid, $object, $context);
  1237. }
  1238. }
  1239. }
  1240. /**
  1241. * Implements hook_flag_access().
  1242. */
  1243. function flag_flag_access($flag, $entity_id, $action, $account) {
  1244. // Do nothing if there is no restriction by authorship.
  1245. if (empty($flag->access_author)) {
  1246. return;
  1247. }
  1248. // Restrict access by authorship. It's important that TRUE is never returned
  1249. // here, otherwise we'd grant permission even if other modules denied access.
  1250. if ($flag->entity_type == 'node') {
  1251. // For non-existent nodes (such as on the node add form), assume that the
  1252. // current user is creating the content.
  1253. if (empty($entity_id) || !($node = $flag->fetch_entity($entity_id))) {
  1254. return $flag->access_author == 'others' ? FALSE : NULL;
  1255. }
  1256. if ($flag->access_author == 'own' && $node->uid != $account->uid) {
  1257. return FALSE;
  1258. }
  1259. elseif ($flag->access_author == 'others' && $node->uid == $account->uid) {
  1260. return FALSE;
  1261. }
  1262. }
  1263. // Restrict access by comment authorship.
  1264. if ($flag->entity_type == 'comment') {
  1265. // For non-existent comments (such as on the comment add form), assume that
  1266. // the current user is creating the content.
  1267. if (empty($entity_id) || !($comment = $flag->fetch_entity($entity_id))) {
  1268. return $flag->access_author == 'comment_others' ? FALSE : NULL;
  1269. }
  1270. $node = node_load($comment->nid);
  1271. if ($flag->access_author == 'node_own' && $node->uid != $account->uid) {
  1272. return FALSE;
  1273. }
  1274. elseif ($flag->access_author == 'node_others' && $node->uid == $account->uid) {
  1275. return FALSE;
  1276. }
  1277. elseif ($flag->access_author == 'comment_own' && $comment->uid != $account->uid) {
  1278. return FALSE;
  1279. }
  1280. elseif ($flag->access_author == 'comment_others' && $comment->uid == $account->uid) {
  1281. return FALSE;
  1282. }
  1283. }
  1284. }
  1285. /**
  1286. * Implements hook_flag_access_multiple().
  1287. */
  1288. function flag_flag_access_multiple($flag, $entity_ids, $account) {
  1289. $access = array();
  1290. // Do nothing if there is no restriction by authorship.
  1291. if (empty($flag->access_author)) {
  1292. return $access;
  1293. }
  1294. if ($flag->entity_type == 'node') {
  1295. // Restrict access by authorship. This is similar to flag_flag_access()
  1296. // above, but returns an array of 'nid' => $access values. Similarly, we
  1297. // should never return TRUE in any of these access values, only FALSE if we
  1298. // want to deny access, or use the current access value provided by Flag.
  1299. $result = db_select('node', 'n')
  1300. ->fields('n', array('nid', 'uid'))
  1301. ->condition('nid', array_keys($entity_ids), 'IN')
  1302. ->condition('type', $flag->types, 'IN')
  1303. ->execute();
  1304. foreach ($result as $row) {
  1305. if ($flag->access_author == 'own') {
  1306. $access[$row->nid] = $row->uid != $account->uid ? FALSE : NULL;
  1307. }
  1308. elseif ($flag->access_author == 'others') {
  1309. $access[$row->nid] = $row->uid == $account->uid ? FALSE : NULL;
  1310. }
  1311. }
  1312. }
  1313. if ($flag->entity_type == 'comment') {
  1314. // Restrict access by comment ownership.
  1315. $query = db_select('comment', 'c');
  1316. $query->leftJoin('node', 'n', 'c.nid = n.nid');
  1317. $query
  1318. ->fields('c', array('cid', 'nid', 'uid'))
  1319. ->condition('c.cid', $entity_ids, 'IN');
  1320. $query->addField('c', 'uid', 'comment_uid');
  1321. $result = $query->execute();
  1322. foreach ($result as $row) {
  1323. if ($flag->access_author == 'node_own') {
  1324. $access[$row->cid] = $row->node_uid != $account->uid ? FALSE : NULL;
  1325. }
  1326. elseif ($flag->access_author == 'node_others') {
  1327. $access[$row->cid] = $row->node_uid == $account->uid ? FALSE : NULL;
  1328. }
  1329. elseif ($flag->access_author == 'comment_own') {
  1330. $access[$row->cid] = $row->comment_uid != $account->uid ? FALSE : NULL;
  1331. }
  1332. elseif ($flag->access_author == 'comment_others') {
  1333. $access[$row->cid] = $row->comment_uid == $account->uid ? FALSE : NULL;
  1334. }
  1335. }
  1336. }
  1337. // Always return an array (even if empty) of accesses.
  1338. return $access;
  1339. }
  1340. /**
  1341. * Implements hook_theme().
  1342. */
  1343. function flag_theme() {
  1344. $path = drupal_get_path('module', 'flag') . '/theme';
  1345. return array(
  1346. 'flag' => array(
  1347. 'variables' => array(
  1348. 'flag' => NULL,
  1349. 'action' => NULL,
  1350. 'entity_id' => NULL,
  1351. 'after_flagging' => FALSE,
  1352. 'needs_wrapping_element' => FALSE,
  1353. 'errors' => array(),
  1354. ),
  1355. 'template' => 'flag',
  1356. 'pattern' => 'flag__',
  1357. 'path' => $path,
  1358. ),
  1359. 'flag_tokens_browser' => array(
  1360. 'variables' => array(
  1361. 'types' => array('all'),
  1362. 'global_types' => TRUE,
  1363. ),
  1364. 'file' => 'flag.tokens.inc',
  1365. ),
  1366. 'flag_admin_listing' => array(
  1367. 'render element' => 'form',
  1368. 'file' => 'includes/flag.admin.inc',
  1369. ),
  1370. 'flag_admin_listing_disabled' => array(
  1371. 'variables' => array(
  1372. 'flags' => NULL,
  1373. 'default_flags' => NULL,
  1374. ),
  1375. 'file' => 'includes/flag.admin.inc',
  1376. ),
  1377. 'flag_admin_page' => array(
  1378. 'variables' => array(
  1379. 'flags' => NULL,
  1380. 'default_flags' => NULL,
  1381. 'flag_admin_listing' => NULL,
  1382. ),
  1383. 'file' => 'includes/flag.admin.inc',
  1384. ),
  1385. 'flag_form_roles' => array(
  1386. 'render element' => 'element',
  1387. 'file' => 'includes/flag.admin.inc',
  1388. ),
  1389. );
  1390. }
  1391. /**
  1392. * A preprocess function for our theme('flag'). It generates the
  1393. * variables needed there.
  1394. *
  1395. * The $variables array initially contains the following arguments:
  1396. * - $flag
  1397. * - $action
  1398. * - $entity_id
  1399. * - $after_flagging
  1400. * - $errors
  1401. * - $needs_wrapping_element
  1402. *
  1403. * See 'flag.tpl.php' for their documentation.
  1404. */
  1405. function template_preprocess_flag(&$variables) {
  1406. global $user;
  1407. $initialized = &drupal_static(__FUNCTION__, array());
  1408. // Some typing shotcuts:
  1409. $flag =& $variables['flag'];
  1410. $action = $variables['action'];
  1411. $entity_id = $variables['entity_id'];
  1412. $errors = implode('<br />', $variables['errors']);
  1413. $flag_name_css = str_replace('_', '-', $flag->name);
  1414. // Generate the link URL.
  1415. $link_type = $flag->get_link_type();
  1416. $link = module_invoke($link_type['module'], 'flag_link', $flag, $action, $entity_id);
  1417. if (isset($link['title']) && empty($link['html'])) {
  1418. $link['title'] = check_plain($link['title']);
  1419. }
  1420. // Replace the link with the access denied text if unable to flag.
  1421. if ($action == 'unflag' && !$flag->access($entity_id, 'unflag')) {
  1422. $link['title'] = $flag->get_label('unflag_denied_text', $entity_id);
  1423. unset($link['href']);
  1424. }
  1425. // Anonymous users always need the JavaScript to maintain their flag state.
  1426. if ($user->uid == 0) {
  1427. $link_type['uses standard js'] = TRUE;
  1428. }
  1429. // Load the JavaScript/CSS, if the link type requires it.
  1430. if (!isset($initialized[$link_type['name']])) {
  1431. if ($link_type['uses standard css']) {
  1432. drupal_add_css(drupal_get_path('module', 'flag') . '/theme/flag.css');
  1433. }
  1434. if ($link_type['uses standard js']) {
  1435. drupal_add_js(drupal_get_path('module', 'flag') . '/theme/flag.js');
  1436. }
  1437. $initialized[$link_type['name']] = TRUE;
  1438. }
  1439. $variables['link'] = $link;
  1440. $variables['link_href'] = isset($link['href']) ? check_url(url($link['href'], $link)) : FALSE;
  1441. $variables['link_text'] = isset($link['title']) ? $link['title'] : $flag->get_label($action . '_short', $entity_id);
  1442. $variables['link_title'] = isset($link['attributes']['title']) ? check_plain($link['attributes']['title']) : check_plain(strip_tags($flag->get_label($action . '_long', $entity_id)));
  1443. $variables['status'] = ($action == 'flag' ? 'unflagged' : 'flagged');
  1444. $variables['flag_name_css'] = $flag_name_css;
  1445. $variables['flag_wrapper_classes_array'] = array();
  1446. $variables['flag_wrapper_classes_array'][] = 'flag-wrapper';
  1447. $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_name_css;
  1448. $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_name_css . '-' . $entity_id;
  1449. $variables['flag_classes_array'] = array();
  1450. $variables['flag_classes_array'][] = 'flag';
  1451. if (isset($link['href'])) {
  1452. $variables['flag_classes_array'][] = $variables['action'] . '-action';
  1453. $variables['flag_classes_array'][] = 'flag-link-' . $flag->link_type;
  1454. }
  1455. else {
  1456. $variables['flag_classes_array'][] = $variables['action'] . '-disabled';
  1457. }
  1458. if (isset($link['attributes']['class'])) {
  1459. $link['attributes']['class'] = is_string($link['attributes']['class']) ? array_filter(explode(' ', $link['attributes']['class'])) : $link['attributes']['class'];
  1460. $variables['flag_classes_array'] = array_merge($variables['flag_classes_array'], $link['attributes']['class']);
  1461. }
  1462. $variables['message_classes_array'] = array();
  1463. if ($variables['after_flagging']) {
  1464. $variables['message_classes_array'][] = 'flag-message';
  1465. if ($errors) {
  1466. $variables['message_classes_array'][] = 'flag-failure-message';
  1467. $variables['message_text'] = $errors;
  1468. }
  1469. else {
  1470. $inverse_action = ($action == 'flag' ? 'unflag' : 'flag');
  1471. $variables['message_classes_array'][] = 'flag-success-message';
  1472. $variables['message_classes_array'][] = 'flag-' . $variables['status'] . '-message';
  1473. $variables['message_text'] = $flag->get_label($inverse_action . '_message', $entity_id);
  1474. $variables['flag_classes_array'][] = $variables['status'];
  1475. // By default we make our JS code remove, after a few seconds, only
  1476. // success messages.
  1477. $variables['message_classes_array'][] = 'flag-auto-remove';
  1478. }
  1479. }
  1480. else {
  1481. $variables['message_text'] = '';
  1482. }
  1483. }
  1484. /**
  1485. * Theme processor for flag.tpl.php.
  1486. *
  1487. * @param array &$variables
  1488. * An array of variables for the template. See 'flag.tpl.php' for their
  1489. * documentation.
  1490. */
  1491. function template_process_flag(&$variables) {
  1492. // Convert class arrays to strings.
  1493. $variables['flag_wrapper_classes'] = implode(' ', $variables['flag_wrapper_classes_array']);
  1494. $variables['flag_classes'] = implode(' ', $variables['flag_classes_array']);
  1495. $variables['message_classes'] = implode(' ', $variables['message_classes_array']);
  1496. }
  1497. /**
  1498. * Return an array of flag names keyed by fid.
  1499. */
  1500. function _flag_get_flag_names() {
  1501. $flags = flag_get_flags();
  1502. $flag_names = array();
  1503. foreach ($flags as $flag) {
  1504. $flag_names[$flag->fid] = $flag->name;
  1505. }
  1506. return $flag_names;
  1507. }
  1508. /**
  1509. * Return an array of flag link types suitable for a select list or radios.
  1510. */
  1511. function _flag_link_type_options() {
  1512. $options = array();
  1513. $types = flag_get_link_types();
  1514. foreach ($types as $type_name => $type) {
  1515. $options[$type_name] = $type['title'];
  1516. }
  1517. return $options;
  1518. }
  1519. /**
  1520. * Return an array of flag link type descriptions.
  1521. */
  1522. function _flag_link_type_descriptions() {
  1523. $options = array();
  1524. $types = flag_get_link_types();
  1525. foreach ($types as $type_name => $type) {
  1526. $options[$type_name] = $type['description'];
  1527. }
  1528. return $options;
  1529. }
  1530. // ---------------------------------------------------------------------------
  1531. // Non-Views public API
  1532. /**
  1533. * Get the count of flags for a particular entity type.
  1534. *
  1535. * When called during a flagging or unflagging (such as from a hook
  1536. * implementation or from Rules), the flagging or unflagging that is in the
  1537. * process of being performed:
  1538. * - will be included during a flagging operation
  1539. * - will STILL be included during an unflagging operation. That is, the count
  1540. * will not yet have been decreased.
  1541. * This is because this queries the {flagging} table, which only has its record
  1542. * deleted at the very end of the unflagging process.
  1543. *
  1544. * @param $flag
  1545. * The flag.
  1546. * @param $entity_type
  1547. * The entity type. For example, 'node'.
  1548. *
  1549. * @return
  1550. * The flag count with the flag name and entity type as the array key.
  1551. */
  1552. function flag_get_entity_flag_counts($flag, $entity_type) {
  1553. $counts = &drupal_static(__FUNCTION__);
  1554. // We check to see if the flag count is already in the cache,
  1555. // if it's not, run the query.
  1556. if (!isset($counts[$flag->name][$entity_type])) {
  1557. $counts[$flag->name][$entity_type] = array();
  1558. $result = db_select('flagging', 'f')
  1559. ->fields('f', array('fid'))
  1560. ->condition('fid', $flag->fid)
  1561. ->condition('entity_type', $entity_type)
  1562. ->countQuery()
  1563. ->execute()
  1564. ->fetchField();
  1565. $counts[$flag->name][$entity_type] = $result;
  1566. }
  1567. return $counts[$flag->name][$entity_type];
  1568. }
  1569. /**
  1570. * Get the user's flag count.
  1571. *
  1572. * When called during a flagging or unflagging (such as from a hook
  1573. * implementation or from Rules), the flagging or unflagging that is in the
  1574. * process of being performed:
  1575. * - will be included during a flagging operation
  1576. * - will STILL be included during an unflagging operation. That is, the count
  1577. * will not yet have been decreased.
  1578. * This is because this queries the {flagging} table, which only has its record
  1579. * deleted at the very end of the unflagging process.
  1580. *
  1581. * @param $flag
  1582. * The flag.
  1583. * @param $user
  1584. * The user object.
  1585. *
  1586. * @return
  1587. * The flag count with the flag name and the uid as the array key.
  1588. */
  1589. function flag_get_user_flag_counts($flag, $user) {
  1590. $counts = &drupal_static(__FUNCTION__);
  1591. // We check to see if the flag count is already in the cache,
  1592. // if it's not, run the query.
  1593. if (!isset($counts[$flag->name][$user->uid])) {
  1594. $counts[$flag->name][$user->uid] = array();
  1595. $result = db_select('flagging', 'f')
  1596. ->fields('f', array('fid'))
  1597. ->condition('fid', $flag->fid)
  1598. ->condition('uid', $user->uid)
  1599. ->countQuery()
  1600. ->execute()
  1601. ->fetchField();
  1602. $counts[$flag->name][$user->uid] = $result;
  1603. }
  1604. return $counts[$flag->name][$user->uid];
  1605. }
  1606. /**
  1607. * Get flag counts for all flags on a node.
  1608. *
  1609. * When called during a flagging or unflagging (such as from a hook
  1610. * implementation or from Rules), the count this returns takes into account the
  1611. * the flagging or unflagging that is in the process of being performed.
  1612. *
  1613. * @param $entity_type
  1614. * The entity type (usually 'node').
  1615. * @param $entity_id
  1616. * The entity ID (usually the node ID).
  1617. *
  1618. * @return
  1619. * The flag count with the flag names as array keys.
  1620. */
  1621. function flag_get_counts($entity_type, $entity_id) {
  1622. $counts = &drupal_static(__FUNCTION__);
  1623. if (!isset($counts[$entity_type][$entity_id])) {
  1624. $counts[$entity_type][$entity_id] = array();
  1625. $query = db_select('flag', 'f');
  1626. $query->leftJoin('flag_counts', 'fc', 'f.fid = fc.fid');
  1627. $result = $query
  1628. ->fields('f', array('name'))
  1629. ->fields('fc', array('count'))
  1630. ->condition('fc.entity_type', $entity_type)
  1631. ->condition('fc.entity_id', $entity_id)
  1632. ->execute();
  1633. foreach ($result as $row) {
  1634. $counts[$entity_type][$entity_id][$row->name] = $row->count;
  1635. }
  1636. }
  1637. return $counts[$entity_type][$entity_id];
  1638. }
  1639. /**
  1640. * Get the total count of items flagged within a flag.
  1641. *
  1642. * When called during a flagging or unflagging (such as from a hook
  1643. * implementation or from Rules), the count this returns takes into account the
  1644. * the flagging or unflagging that is in the process of being performed.
  1645. *
  1646. * @param $flag_name
  1647. * The flag name for which to retrieve a flag count.
  1648. * @param $reset
  1649. * (optional) Reset the internal cache and execute the SQL query another time.
  1650. */
  1651. function flag_get_flag_counts($flag_name, $reset = FALSE) {
  1652. $counts = &drupal_static(__FUNCTION__);
  1653. if ($reset) {
  1654. $counts = array();
  1655. }
  1656. if (!isset($counts[$flag_name])) {
  1657. $flag = flag_get_flag($flag_name);
  1658. $counts[$flag_name] = db_select('flag_counts', 'fc')
  1659. ->fields('fc', array('fid'))
  1660. ->condition('fid', $flag->fid)
  1661. ->countQuery()
  1662. ->execute()
  1663. ->fetchField();
  1664. }
  1665. return $counts[$flag_name];
  1666. }
  1667. /**
  1668. * Load a single flag either by name or by flag ID.
  1669. *
  1670. * @param $name
  1671. * (optional) The flag name.
  1672. * @param $fid
  1673. * (optional) The the flag id.
  1674. *
  1675. * @return
  1676. * The flag object, or FALSE if no matching flag was found.
  1677. */
  1678. function flag_get_flag($name = NULL, $fid = NULL) {
  1679. $flags = flag_get_flags();
  1680. if (isset($name)) {
  1681. if (isset($flags[$name])) {
  1682. return $flags[$name];
  1683. }
  1684. }
  1685. elseif (isset($fid)) {
  1686. foreach ($flags as $flag) {
  1687. if ($flag->fid == $fid) {
  1688. return $flag;
  1689. }
  1690. }
  1691. }
  1692. return FALSE;
  1693. }
  1694. /**
  1695. * List all flags available.
  1696. *
  1697. * If node type or account are entered, a list of all possible flags will be
  1698. * returned.
  1699. *
  1700. * @param $entity_type
  1701. * (optional) The type of entity for which to load the flags. Usually 'node'.
  1702. * @param $content_subtype
  1703. * (optional) The node type for which to load the flags.
  1704. * @param $account
  1705. * (optional) The user accont to filter available flags. If not set, all
  1706. * flags for will this node will be returned.
  1707. *
  1708. * @return
  1709. * An array of the structure [fid] = flag_object.
  1710. */
  1711. function flag_get_flags($entity_type = NULL, $content_subtype = NULL, $account = NULL) {
  1712. $flags = &drupal_static(__FUNCTION__);
  1713. // Retrieve a list of all flags, regardless of the parameters.
  1714. if (!isset($flags)) {
  1715. $flags = array();
  1716. // Database flags.
  1717. $query = db_select('flag', 'f');
  1718. $query->leftJoin('flag_types', 'fn', 'fn.fid = f.fid');
  1719. $result = $query
  1720. ->fields('f', array(
  1721. 'fid',
  1722. 'entity_type',
  1723. 'name',
  1724. 'title',
  1725. 'global',
  1726. 'options',
  1727. ))
  1728. ->fields('fn', array('type'))
  1729. ->execute();
  1730. foreach ($result as $row) {
  1731. if (!isset($flags[$row->name])) {
  1732. $flags[$row->name] = flag_flag::factory_by_row($row);
  1733. }
  1734. else {
  1735. $flags[$row->name]->types[] = $row->type;
  1736. }
  1737. }
  1738. // Add code-based flags provided by modules.
  1739. $default_flags = flag_get_default_flags();
  1740. foreach ($default_flags as $name => $default_flag) {
  1741. // Insert new enabled flags into the database to give them an FID.
  1742. if ($default_flag->status && !isset($flags[$name])) {
  1743. $default_flag->save();
  1744. $flags[$name] = $default_flag;
  1745. }
  1746. if (isset($flags[$name])) {
  1747. // Ensure overridden flags are associated with their parent module.
  1748. $flags[$name]->module = $default_flag->module;
  1749. // Update the flag with any properties that are "locked" by the code
  1750. // version.
  1751. if (isset($default_flag->locked)) {
  1752. $flags[$name]->locked = $default_flag->locked;
  1753. foreach ($default_flag->locked as $property) {
  1754. $flags[$name]->$property = $default_flag->$property;
  1755. }
  1756. }
  1757. }
  1758. }
  1759. // Sort the list of flags by weight.
  1760. uasort($flags, '_flag_compare_weight');
  1761. foreach ($flags as $flag) {
  1762. // Allow modules implementing hook_flag_alter(&$flag) to modify each flag.
  1763. drupal_alter('flag', $flag);
  1764. }
  1765. }
  1766. // Make a variable copy to filter types and account.
  1767. $filtered_flags = $flags;
  1768. // Filter out flags based on type and subtype.
  1769. if (isset($entity_type) || isset($content_subtype)) {
  1770. foreach ($filtered_flags as $name => $flag) {
  1771. if (!$flag->access_entity_enabled($entity_type, $content_subtype)) {
  1772. unset($filtered_flags[$name]);
  1773. }
  1774. }
  1775. }
  1776. // Filter out flags based on account permissions.
  1777. if (isset($account) && $account->uid != 1) {
  1778. foreach ($filtered_flags as $name => $flag) {
  1779. // We test against the 'flag' action, which is the minimum permission to
  1780. // use a flag.
  1781. if (!$flag->user_access('flag', $account)) {
  1782. unset($filtered_flags[$name]);
  1783. }
  1784. }
  1785. }
  1786. return $filtered_flags;
  1787. }
  1788. /**
  1789. * Comparison function for uasort().
  1790. */
  1791. function _flag_compare_weight($flag1, $flag2) {
  1792. if ($flag1->weight == $flag2->weight) {
  1793. return 0;
  1794. }
  1795. return $flag1->weight < $flag2->weight ? -1 : 1;
  1796. }
  1797. /**
  1798. * Retrieve a list of flags defined by modules.
  1799. *
  1800. * @param $include_disabled
  1801. * (optional) Unless specified, only enabled flags will be returned.
  1802. *
  1803. * @return
  1804. * An array of flag prototypes, not usable for flagging. Use flag_get_flags()
  1805. * if needing to perform a flagging with any enabled flag.
  1806. */
  1807. function flag_get_default_flags($include_disabled = FALSE) {
  1808. $default_flags = array();
  1809. $flag_status = variable_get('flag_default_flag_status', array());
  1810. $default_flags_info = array();
  1811. foreach (module_implements('flag_default_flags') as $module) {
  1812. $function = $module . '_flag_default_flags';
  1813. foreach ($function() as $flag_name => $flag_info) {
  1814. // Backward compatibility: old exported default flags have their names
  1815. // in $flag_info instead, so we use the + operator to not overwrite it.
  1816. $default_flags_info[$flag_name] = $flag_info + array(
  1817. 'name' => $flag_name,
  1818. 'module' => $module,
  1819. );
  1820. }
  1821. }
  1822. // Allow modules to alter definitions using hook_flag_default_flags_alter().
  1823. drupal_alter('flag_default_flags', $default_flags_info);
  1824. foreach ($default_flags_info as $flag_info) {
  1825. $flag = flag_flag::factory_by_array($flag_info);
  1826. // Disable flags that are not at the current API version.
  1827. if (!$flag->is_compatible()) {
  1828. $flag->status = FALSE;
  1829. }
  1830. // Add flags that have been enabled.
  1831. if ((!isset($flag_status[$flag->name]) && (!isset($flag->status) || $flag->status)) || !empty($flag_status[$flag->name])) {
  1832. $flag->status = TRUE;
  1833. $default_flags[$flag->name] = $flag;
  1834. }
  1835. // Add flags that have been disabled.
  1836. elseif ($include_disabled) {
  1837. $flag->status = FALSE;
  1838. $default_flags[$flag->name] = $flag;
  1839. }
  1840. }
  1841. return $default_flags;
  1842. }
  1843. /**
  1844. * Get all flagged entities in a flag.
  1845. *
  1846. * @param $flag_name
  1847. * The flag name for which to retrieve flagged entites.
  1848. *
  1849. * @return
  1850. * An array of flagging data, keyed by the flagging ID.
  1851. */
  1852. function flag_get_flag_flagging_data($flag_name) {
  1853. $flag = flag_get_flag($flag_name);
  1854. $result = db_select('flagging', 'fc')
  1855. ->fields('fc')
  1856. ->condition('fid', $flag->fid)
  1857. ->execute();
  1858. return $result->fetchAllAssoc('flagging_id');
  1859. }
  1860. /**
  1861. * Find what a user has flagged, either a single entity or on the entire site.
  1862. *
  1863. * When called during a flagging or unflagging (such as from a hook
  1864. * implementation or from Rules), the flagging or unflagging that is in the
  1865. * process of being performed:
  1866. * - will be included during a flagging operation
  1867. * - will STILL be included during an unflagging operation. That is, the count
  1868. * will not yet have been decreased.
  1869. * This is because this queries the {flagging} table, which only has its record
  1870. * deleted at the very end of the unflagging process.
  1871. *
  1872. * @param $entity_type
  1873. * The type of entity that will be retrieved. Usually 'node'.
  1874. * @param $entity_id
  1875. * (optional) The entity ID to check for flagging. If none given, all
  1876. * entities flagged by this user will be returned.
  1877. * @param $uid
  1878. * (optional) The user ID whose flags we're checking. If none given, the
  1879. * current user will be used.
  1880. * @param $sid
  1881. * (optional) The user SID (provided by Session API) whose flags we're
  1882. * checking. If none given, the current user will be used. The SID is 0 for
  1883. * logged in users.
  1884. *
  1885. * @return
  1886. * If returning a single item's flags (that is, when $entity_id isn't NULL),
  1887. * an array of the structure
  1888. * [flag_name] => (
  1889. * flagging_id => [flagging_id],
  1890. * uid => [uid],
  1891. * entity_id => [entity_id],
  1892. * timestamp => [timestamp],
  1893. * ...)
  1894. *
  1895. * If returning all items' flags, an array of arrays for each flag:
  1896. * [flag_name] => [entity_id] => Object from above.
  1897. */
  1898. function flag_get_user_flags($entity_type, $entity_id = NULL, $uid = NULL, $sid = NULL) {
  1899. $flagged_content = &drupal_static(__FUNCTION__);
  1900. $uid = !isset($uid) ? $GLOBALS['user']->uid : $uid;
  1901. $sid = !isset($sid) ? flag_get_sid($uid) : $sid;
  1902. if (isset($entity_id)) {
  1903. if (!isset($flagged_content[$uid][$sid][$entity_type][$entity_id])) {
  1904. $flag_names = _flag_get_flag_names();
  1905. $flagged_content[$uid][$sid][$entity_type][$entity_id] = array();
  1906. $result = db_select('flagging', 'fc')
  1907. ->fields('fc')
  1908. ->condition('entity_type', $entity_type)
  1909. ->condition('entity_id', $entity_id)
  1910. ->condition(db_or()
  1911. ->condition('uid', $uid)
  1912. ->condition('uid', 0)
  1913. )
  1914. ->condition('sid', $sid)
  1915. ->execute();
  1916. foreach ($result as $flagging_data) {
  1917. $flagged_content[$uid][$sid][$entity_type][$entity_id][$flag_names[$flagging_data->fid]] = $flagging_data;
  1918. }
  1919. }
  1920. return $flagged_content[$uid][$sid][$entity_type][$entity_id];
  1921. }
  1922. else {
  1923. if (!isset($flagged_content[$uid][$sid][$entity_type]['all'])) {
  1924. $flag_names = _flag_get_flag_names();
  1925. $flagged_content[$uid][$sid][$entity_type]['all'] = array();
  1926. $result = db_select('flagging', 'fc')
  1927. ->fields('fc')
  1928. ->condition('entity_type', $entity_type)
  1929. ->condition(db_or()
  1930. ->condition('uid', $uid)
  1931. ->condition('uid', 0)
  1932. )
  1933. ->condition('sid', $sid)
  1934. ->execute();
  1935. foreach ($result as $flagging_data) {
  1936. $flagged_content[$uid][$sid][$entity_type]['all'][$flag_names[$flagging_data->fid]][$flagging_data->entity_id] = $flagging_data;
  1937. }
  1938. }
  1939. return $flagged_content[$uid][$sid][$entity_type]['all'];
  1940. }
  1941. }
  1942. /**
  1943. * Return a list of users who have flagged an entity.
  1944. *
  1945. * When called during a flagging or unflagging (such as from a hook
  1946. * implementation or from Rules), the flagging or unflagging that is in the
  1947. * process of being performed:
  1948. * - will be included during a flagging operation
  1949. * - will STILL be included during an unflagging operation. That is, the count
  1950. * will not yet have been decreased.
  1951. * This is because this queries the {flagging} table, which only has its record
  1952. * deleted at the very end of the unflagging process.
  1953. *
  1954. * @param $entity_type
  1955. * The type of entity that will be retrieved. Usually 'node'.
  1956. * @param $entity_id
  1957. * The entity ID to check for flagging.
  1958. * @param $flag_name
  1959. * (optional) The name of a flag if wanting a list specific to a single flag.
  1960. *
  1961. * @return
  1962. * A nested array of flagging records (i.e. rows from the {flagging} table,
  1963. * rather than complete Flagging entities). The structure depends on the
  1964. * presence of the $flag_name parameter:
  1965. * - if $flag_name is omitted, the array is keyed first by the user ID of
  1966. * the users that flagged the entity, then by flag name. Each value is
  1967. * then the flagging record.
  1968. * - if $flag_name is given, the array is keyed only by user ID. Each value
  1969. * is the flagging record.
  1970. * If no flags were found an empty array is returned.
  1971. */
  1972. function flag_get_entity_flags($entity_type, $entity_id, $flag_name = NULL) {
  1973. $entity_flags = &drupal_static(__FUNCTION__, array());
  1974. if (!isset($entity_flags[$entity_type][$entity_id])) {
  1975. $flag_names = _flag_get_flag_names();
  1976. $result = db_select('flagging', 'fc')
  1977. ->fields('fc')
  1978. ->condition('entity_type', $entity_type)
  1979. ->condition('entity_id', $entity_id)
  1980. ->orderBy('timestamp', 'DESC')
  1981. ->execute();
  1982. $entity_flags[$entity_type][$entity_id] = array();
  1983. foreach ($result as $flagging_data) {
  1984. // Build a list of flaggings for all flags by user.
  1985. $entity_flags[$entity_type][$entity_id]['users'][$flagging_data->uid][$flag_names[$flagging_data->fid]] = $flagging_data;
  1986. // Build a list of flaggings for each individual flag.
  1987. $entity_flags[$entity_type][$entity_id]['flags'][$flag_names[$flagging_data->fid]][$flagging_data->uid] = $flagging_data;
  1988. }
  1989. }
  1990. if (empty($entity_flags[$entity_type][$entity_id])) {
  1991. return array();
  1992. }
  1993. if (isset($flag_name)) {
  1994. if (isset($entity_flags[$entity_type][$entity_id]['flags'][$flag_name])) {
  1995. return $entity_flags[$entity_type][$entity_id]['flags'][$flag_name];
  1996. }
  1997. return array();
  1998. }
  1999. return $entity_flags[$entity_type][$entity_id]['users'];
  2000. }
  2001. /**
  2002. * A utility function for outputting a flag link.
  2003. *
  2004. * You should call this function from your template when you want to put the
  2005. * link on the page yourself. For example, you could call this function from
  2006. * your theme preprocessor for node.tpl.php:
  2007. * @code
  2008. * $variables['my_flag_link'] = flag_create_link('bookmarks', $node->nid);
  2009. * @endcode
  2010. *
  2011. * @param $flag_name
  2012. * The "machine readable" name of the flag; e.g. 'bookmarks'.
  2013. * @param $entity_id
  2014. * The entity ID to check for flagging, for example a node ID.
  2015. * @param $variables
  2016. * An array of further variables to pass to theme('flag'). For the full list
  2017. * of parameters, see flag.tpl.php. Of particular interest:
  2018. * - after_flagging: Set to TRUE if this flag link is being displayed as the
  2019. * result of a flagging action.
  2020. * - errors: An array of error messages.
  2021. *
  2022. * @return
  2023. * The HTML for the themed flag link.
  2024. */
  2025. function flag_create_link($flag_name, $entity_id, $variables = array()) {
  2026. $flag = flag_get_flag($flag_name);
  2027. if (!$flag) {
  2028. // Flag does not exist.
  2029. return;
  2030. }
  2031. if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
  2032. // User has no permission to use this flag.
  2033. return;
  2034. }
  2035. return $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, $variables);
  2036. }
  2037. /**
  2038. * Trim a flag to a certain size.
  2039. *
  2040. * @param $fid
  2041. * The flag object.
  2042. * @param $account
  2043. * The user object on behalf the trimming will occur.
  2044. * @param $cutoff_size
  2045. * The number of flaggings allowed. Any flaggings beyond that will be trimmed.
  2046. * @param $trim_newest
  2047. * An optional boolean indicating whether to trim the newest flags.
  2048. * @param $permissions_check
  2049. * (optional) A boolean indicating whether to skip permissions.
  2050. * This will trim the flag if $permissions_check is TRUE even if the user
  2051. * doesn't have the permission to flag/unflag.
  2052. */
  2053. function flag_trim_flag($flag, $account, $cutoff_size, $trim_newest, $permissions_check = FALSE) {
  2054. $query = db_select('flagging', 'fc')
  2055. ->fields('fc')
  2056. ->condition('fid', $flag->fid)
  2057. ->condition(db_or()->condition('uid', $account->uid)->condition('uid', 0))
  2058. // Account for session ID (in the case of anonymous users).
  2059. ->condition('sid', flag_get_sid($account->uid));
  2060. // If $trim_newest is TRUE, then, we should order by 'ASC' as we should trim
  2061. // the newest flags.
  2062. if ($trim_newest) {
  2063. $query->orderBy('timestamp', 'ASC');
  2064. }
  2065. else {
  2066. $query->orderBy('timestamp', 'DESC');
  2067. }
  2068. // Execute the query.
  2069. $result = $query->execute();
  2070. $i = 1;
  2071. foreach ($result as $row) {
  2072. if ($i++ > $cutoff_size) {
  2073. flag('unflag', $flag->name, $row->entity_id, $account, $permissions_check);
  2074. }
  2075. }
  2076. }
  2077. /**
  2078. * Remove all flagged entities from a flag.
  2079. *
  2080. * @param $flag
  2081. * The flag object.
  2082. * @param $entity_id
  2083. * (optional) The entity ID on which all flaggings will be removed. If left
  2084. * empty, this will remove all of this flag's entities.
  2085. */
  2086. function flag_reset_flag($flag, $entity_id = NULL) {
  2087. $query = db_select('flagging', 'fc')
  2088. ->fields('fc')
  2089. ->condition('fid', $flag->fid);
  2090. if ($entity_id) {
  2091. $query->condition('entity_id', $entity_id);
  2092. }
  2093. $result = $query->execute()->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
  2094. $rows = array();
  2095. foreach ($result as $row) {
  2096. $rows[] = $row;
  2097. }
  2098. module_invoke_all('flag_reset', $flag, $entity_id, $rows);
  2099. $query = db_delete('flagging')->condition('fid', $flag->fid);
  2100. // Update the flag_counts table.
  2101. $count_query = db_delete('flag_counts')->condition('fid', $flag->fid);
  2102. if ($entity_id) {
  2103. $query->condition('entity_id', $entity_id);
  2104. $count_query->condition('entity_id', $entity_id);
  2105. }
  2106. $count_query->execute();
  2107. return $query->execute();
  2108. }
  2109. /**
  2110. * Return an array of link types provided by modules.
  2111. *
  2112. * @return
  2113. * An array of link types as defined by hook_flag_link_type_info(). These are
  2114. * keyed by the type name, and each value is an array of properties. In
  2115. * addition to those defined in hook_flag_link_type_info(), the following
  2116. * properties are set:
  2117. * - 'module': The providing module.
  2118. * - 'name': The machine name of the type.
  2119. *
  2120. * @see hook_flag_link_type_info()
  2121. * @see hook_flag_link_type_info_alter()
  2122. */
  2123. function flag_get_link_types() {
  2124. $link_types = &drupal_static(__FUNCTION__);
  2125. if (!isset($link_types)) {
  2126. if ($cache = cache_get('flag_link_type_info')) {
  2127. $link_types = $cache->data;
  2128. }
  2129. // In some rare edge cases cache_get() can return an empty result. If it
  2130. // does, we make sure to fetch the link types again.
  2131. if (empty($link_types)) {
  2132. $link_types = array();
  2133. foreach (module_implements('flag_link_type_info') as $module) {
  2134. $module_types = module_invoke($module, 'flag_link_type_info');
  2135. foreach ($module_types as $type_name => $info) {
  2136. $link_types[$type_name] = $info + array(
  2137. 'module' => $module,
  2138. 'name' => $type_name,
  2139. 'title' => '',
  2140. 'description' => '',
  2141. 'options' => array(),
  2142. 'uses standard js' => TRUE,
  2143. 'uses standard css' => TRUE,
  2144. 'provides form' => FALSE,
  2145. );
  2146. }
  2147. }
  2148. drupal_alter('flag_link_type_info', $link_types);
  2149. cache_set('flag_link_type_info', $link_types);
  2150. }
  2151. }
  2152. return $link_types;
  2153. }
  2154. /**
  2155. * Get a private token used to protect links from spoofing - CSRF.
  2156. */
  2157. function flag_get_token($entity_id) {
  2158. // Anonymous users get a less secure token, since it must be the same for all
  2159. // anonymous users on the entire site to work with page caching.
  2160. return ($GLOBALS['user']->uid) ? drupal_get_token($entity_id) : md5(drupal_get_private_key() . $entity_id);
  2161. }
  2162. /**
  2163. * Check to see if a token value matches the specified node.
  2164. */
  2165. function flag_check_token($token, $entity_id) {
  2166. return flag_get_token($entity_id) == $token;
  2167. }
  2168. /**
  2169. * Set the Session ID for a user. Utilizes the Session API module.
  2170. *
  2171. * Creates a Session ID for an anonymous user and returns it. It will always
  2172. * return 0 for registered users.
  2173. *
  2174. * @param int $uid
  2175. * (optional) The user ID to create a session ID for. Defaults to the
  2176. * current user.
  2177. * @param bool $create
  2178. * (optional) Determines whether a session should be created if it doesn't
  2179. * exist yet. Defaults to TRUE.
  2180. *
  2181. * @return
  2182. * The session ID, if a session was created. If not, the return value is 0.
  2183. *
  2184. * @see flag_get_sid()
  2185. */
  2186. function flag_set_sid($uid = NULL, $create = TRUE) {
  2187. $sids = &drupal_static(__FUNCTION__, array());
  2188. if (!isset($uid)) {
  2189. $uid = $GLOBALS['user']->uid;
  2190. }
  2191. // Set the sid if none has been set yet. If the caller specified to create an
  2192. // sid and we have an invalid one (-1), create it.
  2193. if (!isset($sids[$uid]) || ($sids[$uid] == -1 && $create)) {
  2194. if (module_exists('session_api') && session_api_available() && $uid == 0) {
  2195. // This returns one of the following:
  2196. // - -1. This indicates that no session exists and none was created.
  2197. // - A positive integer with the Session ID when it does exist.
  2198. $sids[$uid] = session_api_get_sid($create);
  2199. }
  2200. else {
  2201. $sids[$uid] = 0;
  2202. }
  2203. }
  2204. // Keep the -1 case internal and let the outside world only distinguish two
  2205. // cases: (1) there is an SID; (2) there is no SID (-> 0).
  2206. return $sids[$uid] == -1 ? 0 : $sids[$uid];
  2207. }
  2208. /**
  2209. * Get the Session ID for a user. Utilizes the Session API module.
  2210. *
  2211. * Gets the Session ID for an anonymous user. It will always return 0 for
  2212. * registered users.
  2213. *
  2214. * @param int $uid
  2215. * (optional) The user ID to return the session ID for. Defaults to the
  2216. * current user.
  2217. * @param bool $create
  2218. * (optional) Determines whether a session should be created if it doesn't
  2219. * exist yet. Defaults to FALSE.
  2220. *
  2221. * @return
  2222. * The session ID, if the session exists. If not, the return value is 0.
  2223. *
  2224. * @see flag_set_sid()
  2225. */
  2226. function flag_get_sid($uid = NULL, $create = FALSE) {
  2227. return flag_set_sid($uid, $create);
  2228. }
  2229. // ---------------------------------------------------------------------------
  2230. // Drupal Core operations
  2231. /**
  2232. * Implements hook_node_operations().
  2233. *
  2234. * Add additional options on the admin/build/node page.
  2235. */
  2236. function flag_node_operations() {
  2237. global $user;
  2238. $flags = flag_get_flags('node', NULL, $user);
  2239. $operations = array();
  2240. foreach ($flags as $flag) {
  2241. $operations['flag_' . $flag->name] = array(
  2242. 'label' => $flag->get_label('flag_short'),
  2243. 'callback' => 'flag_nodes',
  2244. 'callback arguments' => array('flag', $flag->name),
  2245. 'behavior' => array(),
  2246. );
  2247. $operations['unflag_' . $flag->name] = array(
  2248. 'label' => $flag->get_label('unflag_short'),
  2249. 'callback' => 'flag_nodes',
  2250. 'callback arguments' => array('unflag', $flag->name),
  2251. 'behavior' => array(),
  2252. );
  2253. }
  2254. return $operations;
  2255. }
  2256. /**
  2257. * Callback function for hook_node_operations().
  2258. */
  2259. function flag_nodes($nodes, $action, $flag_name) {
  2260. $performed = FALSE;
  2261. foreach ($nodes as $nid) {
  2262. $performed |= flag($action, $flag_name, $nid);
  2263. }
  2264. if ($performed) {
  2265. drupal_set_message(t('The update has been performed.'));
  2266. }
  2267. }
  2268. /**
  2269. * Implements hook_user_operations().
  2270. */
  2271. function flag_user_operations() {
  2272. global $user;
  2273. $flags = flag_get_flags('user', NULL, $user);
  2274. $operations = array();
  2275. foreach ($flags as $flag) {
  2276. $operations['flag_' . $flag->name] = array(
  2277. 'label' => $flag->get_label('flag_short'),
  2278. 'callback' => 'flag_users',
  2279. 'callback arguments' => array('flag', $flag->name),
  2280. );
  2281. $operations['unflag_' . $flag->name] = array(
  2282. 'label' => $flag->get_label('unflag_short'),
  2283. 'callback' => 'flag_users',
  2284. 'callback arguments' => array('unflag', $flag->name),
  2285. );
  2286. }
  2287. return $operations;
  2288. }
  2289. /**
  2290. * Callback function for hook_user_operations().
  2291. */
  2292. function flag_users($users, $action, $flag_name) {
  2293. foreach ($users as $uid) {
  2294. flag($action, $flag_name, $uid);
  2295. }
  2296. }
  2297. // ---------------------------------------------------------------------------
  2298. // Contrib integration hooks
  2299. /**
  2300. * Implements hook_views_api().
  2301. */
  2302. function flag_views_api() {
  2303. return array(
  2304. 'api' => 3.0,
  2305. 'path' => drupal_get_path('module', 'flag') . '/includes/views',
  2306. );
  2307. }
  2308. /**
  2309. * Implements hook_features_api().
  2310. */
  2311. function flag_features_api() {
  2312. return array(
  2313. 'flag' => array(
  2314. 'name' => t('Flag'),
  2315. 'feature_source' => TRUE,
  2316. 'default_hook' => 'flag_default_flags',
  2317. 'file' => drupal_get_path('module', 'flag') . '/includes/flag.features.inc',
  2318. ),
  2319. );
  2320. }
  2321. /**
  2322. * Implements hook_ctools_plugin_directory().
  2323. */
  2324. function flag_ctools_plugin_directory($module, $plugin) {
  2325. if ($module == 'ctools' && !empty($plugin)) {
  2326. return "plugins/$plugin";
  2327. }
  2328. }
  2329. // ---------------------------------------------------------------------------
  2330. // Entity Metadata callbacks
  2331. /**
  2332. * Getter callback that returns whether the given entity is flagged.
  2333. */
  2334. function flag_properties_get_flagging_boolean($entity, array $options, $name, $entity_type, $property_info) {
  2335. list($entity_id,) = entity_extract_ids($entity_type, $entity);
  2336. $flagging_data = flag_get_user_flags($entity_type, $entity_id);
  2337. return isset($flagging_data[$property_info['flag_name']]);
  2338. }
  2339. /**
  2340. * Getter callback that returns entities the given user flagged.
  2341. */
  2342. function flag_properties_get_flagged_entities($entity, array $options, $name, $entity_type, $property_info) {
  2343. // Need the entity type the flag applies to.
  2344. $flag_entity_type = $property_info['flag_entity_type'];
  2345. $flagging_data = flag_get_user_flags($flag_entity_type, NULL, $entity->uid);
  2346. $flag_name = $property_info['flag_name'];
  2347. if (isset($flagging_data[$flag_name])) {
  2348. return array_keys($flagging_data[$flag_name]);
  2349. }
  2350. return array();
  2351. }
  2352. /**
  2353. * Getter callback that returns users who flagged the given entity.
  2354. */
  2355. function flag_properties_get_flagging_users($entity, array $options, $name, $entity_type, $property_info) {
  2356. list($entity_id,) = entity_extract_ids($entity_type, $entity);
  2357. $flagging_data = flag_get_entity_flags($entity_type, $entity_id, $property_info['flag_name']);
  2358. return array_keys($flagging_data);
  2359. }
  2360. /**
  2361. * Getter callback that returns the SID of the user that is being retrieved.
  2362. *
  2363. * Callback for hook_entity_property_info_alter().
  2364. *
  2365. * @param stdobj $entity
  2366. * The entity object representing a user for which we are getting inforamtion for.
  2367. *
  2368. * @param array $options
  2369. * Options reguarding the nature of the entity. Language, etc.
  2370. *
  2371. * @param string $name
  2372. * The name of the property we are running this callback for.
  2373. *
  2374. * @param string $entity_type
  2375. * The type that the stdobj $entity is supposed to be.
  2376. *
  2377. * @param $property_info
  2378. * The ifnromatin that represents the property we are providing a result for.
  2379. *
  2380. * @return an integer representing the user's sid field from the session_api table
  2381. *
  2382. * @ingroup callbacks
  2383. */
  2384. function flag_properties_get_user_sid($entity, array $options, $name, $entity_type, $property_info) {
  2385. $sid = flag_get_sid($entity->uid, FALSE);
  2386. return $sid;
  2387. }