flag.module 85 KB

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